LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

P-luminary

SpringMVC[Mapping,中文乱码,Restful及跨域,JSON序列化,非简单请求,拦截器]

2024/1/15

Spring MVC

内容 说明 重要程度
Spring MVC入门 Spring MVC开发流程与环境配置 ★★★★★★
接收Web数据 Spring MVC参数接收与数据绑定 ★★★★★★
URL Mapping 讲解URL绑定过程 ★★★★★★
中文乱码问题 解决请求与相应中文乱码 ★★★★★★
拦截器 Spring MVC拦截器的使用 ★★★

Restful开发风格 [主流]

内容 说明 重要程度
Restful风格介绍 介绍Restful开发规范 ★★★★★★
Restful开发实战 实例讲解Restful在Spring MVC中的实现 ★★★★★★
JSON序列化 通过相应输出数据 ★★★★★★
Restful的跨域问题 分析跨域问题的来源与解决办法 ★★★★★★

Spring MVC[Model+View+Controller(中介)]

比servlet方便许多 简化web程序的开发

  • Spring MVC是Spring体系的轻量级Web MVC框架
  • Spring MVC的核心Controller控制器,用于处理请求,产生相应
  • Spring MVC基于Spring IOC容器运行,所有对象被IoC管理
学习向导
  • Spring MVC入门
  • Spring MVC数据绑定
  • Restful 开发风格
  • Spring MVC拦截器

Spring MVC环境配置

IDEA环境下创建Maven WebApp

Project Structure 点加号 添加Web 右侧Path是Web描述符所存储的路径
C:\Users\Pluminary\Desktop\SpringMVC\src\main\webapp\WEB-INF\web.xml
Deployment descriptor version => 3.1
下面的 Web Resource Directory【用于目录存储页面】
C:\Users\Pluminary\Desktop\SpringMVC\src\main\webapp
再次点下方的Create Artifact
右上角Type:
Web Application: Exploded 目录方式运行
Web Application: Archive 打包成war包运行
配置Tomcat Server => Deployment 添加当前工程 SpringMVC:Web exploded
下面的Application context: 设置为 / 只访问localhost即可
Server选项框里的 On ‘Update’ action: 把Restart server 改成 Update classes and resources【热部署】
当页面源代码发生变化时 不需要重启

  • Maven依赖Spring-WebMVC
  • web.xml配置DispatcherServlet
  • 配置applicationContext的mvc标记
  • 开发Controller控制器
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>first-springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>
    </dependencies>
</project>
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
<!--    DispatchServlet 对所有请求进行拦截  -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
<!--        DispatcherServlet是Spring MVC是最核心的对象
            DispatcherServlet用于拦截http请求
            并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理
-->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--        在Web应用启动时自动创建Spring IoC容器,并初始化DispatcherServlet -->
<!--        applicationContext.xml 初始化参数 启动时候加载-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
<!--        "/" 所有请求都要拦截-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- context:component-scan标签作用 在Spring IoC初始化过程中, 自动创建并管理com.imooc.springmvc
   及子包中拥有以下注解的对象:
       @Repository 通常存放在Dao类上 通常都是与数据发生直接交互的类
       @Service     ...Service类上 业务逻辑类
       @Controller 描述SpringMVC的控制器类
       @Component  无法确定的类型种类
   -->
    <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
<!--    启用Spring MVC的注解开发模式-->
    <mvc:annotation-driven/>
<!--    将图片/JS/CSS等静态资源排除在外, 可提高执行效率-->
    <mvc:default-servlet-handler/>
</beans>
com/imooc/springmvc/controller/TestController.java
package com.imooc.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {
//  将当前的方法绑定某个get方式请求的url  //localhost/t
    @GetMapping("/t")
//  直接向响应输出字符串数据,不跳转页面
    @ResponseBody
    public String test(){
        return "SUCCESS";
    }
}

添加依赖jar包进入项目工程 配置tomcat服务Run/Debug Configurations 中的 Deployment 点一下小铅笔修改 将右侧的Avaliable Elements中的依赖包全部put进入
404bug 配置都正常无误 寻找一下项目工程里的out/artifacts/WEB-INF/web.xml

Spring MVC数据绑定

URL Mapping(URL映射)
  • URL Mapping指将URL与Controller方法绑定
  • 通过将URL与方法绑定,SpringMVC便可通过Tomcat对外暴露服务
URL Mapping注解
  • @RequestMapping - 通用绑定 //在全局用则是通用请求映射访问前缀 若在方法上则不区分get/post请求
  • @GetMapping - 绑定Get请求
  • @PostMapping - 绑定Post请求

@GetMapping("/g") ====== @RequestMapping(value="/g",method=RequestMethod.GET)

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>first-springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
    </dependencies>
</project>
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--    DispatchServlet 对所有请求进行拦截  -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <!--        DispatcherServlet是Spring MVC是最核心的对象
                    DispatcherServlet用于拦截http请求
                    并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理
        -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--        在Web应用启动时自动创建Spring IoC容器,并初始化DispatcherServlet -->
        <!--        applicationContext.xml 初始化参数 启动时候加载-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--        "/" 所有请求都要拦截-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- context:component-scan标签作用 在Spring IoC初始化过程中, 自动创建并管理com.imooc.springmvc
       及子包中拥有以下注解的对象:
           @Repository 通常存放在Dao类上 通常都是与数据发生直接交互的类
           @Service     ...Service类上 业务逻辑类
           @Controller 描述SpringMVC的控制器类
           @Component  无法确定的类型种类
       -->
    <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
    <!--    启用Spring MVC的注解开发模式-->
    <mvc:annotation-driven/>
    <!--    将图片/JS/CSS等静态资源排除在外, 可提高执行效率-->
    <mvc:default-servlet-handler/>
</beans>
com/imooc/springmvc/controller/TestController.java
package com.imooc.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {
    @GetMapping("/t") //localhost/t
    @ResponseBody //直接向响应输出字符串数据,不跳转页面
    public String test(){
        return "Hello Spring MVC";
    }
}
com/imooc/springmvc/controller/URLMappingController.java
package com.imooc.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping ("/um") //类上直接使用默认访问前缀 全局通用请求映射
public class URLMappingController {
    @GetMapping("/g")
    @ResponseBody
    public String getMapping(){
        return "This is get method";
    }
//  直接访问会出错 post请求如何访问 用进行html表单提交
    @PostMapping("/p")
    @ResponseBody
    public String postMapping(){
        return "This is post method";
    }
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/um/p" method="post">
        <input type="submit" value="提交">
    </form>
</body>
</html>

Controller方法参数接收请求参数

接收请求参数的常用做法
  • 使用Controller方法参数接收
<form action="/m1" method="post">
    <input name="username"/>
    <input name="password"/>
</form>
----------------------------------------------------
@PostMapping("/m1")
@ResponseBody
public String post(String username, Long password){
    return username + ":" + password;
}

特殊的注解@RequestParam("") 接收特殊自定义的参数

com/imooc/springmvc/controller/URLMappingController.java
package com.imooc.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/um") //类上直接使用默认访问前缀 全局通用请求映射
public class URLMappingController {
    @GetMapping("/g")
    @ResponseBody
    public String getMapping(@RequestParam("manager_name") String managerName) {
        System.out.println("managerName:"+managerName);
        return "This is get method";
    }

    //    直接访问会出错 post请求如何访问 用进行html表单提交
    @PostMapping("/p")
    @ResponseBody
    public String postMapping(String username, String password) {
        System.out.println(username + ":" + password);
        return "This is post method";
    }
}

=========================
网页中输入 http://localhost/um/g?manager_name=lily
控制台返回 managerName:lily
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/um/p" method="post">
        <input name="username"><br/>
        <input name="password"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>
  • 使用Java Bean接收数据
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/um/p1" method="post">
        <input name="username"><br/>
        <input name="password"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>

​ 用实体对象User进行接收多个对象的创建和注入以及类型转换 [一次性完成]

com/imooc/springmvc/controller/URLMappingController.java
@PostMapping("/p1")
    @ResponseBody //只要拥有User属性和参数 就可以一起赋值
    public String postMapping1(User user, String username){
        System.out.println(user.getUsername() + ":" + user.getPassword());
        return "This is post method";
    }
User.java
//标准的java bean
public class User {
    private String username;
    private Long password;
} Setter + Getter

踩坑记录:IDEA web项目out artifacts文件夹只包含WEB-INF_out artifacts无法生成test.html-CSDN博客

综合训练:学员调查问卷

知识点
  • 利用数组或者List接收请求中的复合数据
  • 利用@RequestParam为参数设置默认值
  • 使用Map对象接收请求参数及注意事项
URI绝对路径与相对路径

相对地址的应用案例
src/main/webapp/form.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学员调查问卷</title>
    <style>
        .container {
            position: absolute;
            border: 1px solid #cccccc;
            left: 50%;
            top: 50%;
            width: 400px;
            height: 300px;
            margin-left: -200px;
            margin-top: -150px;
            box-sizing: border-box;
            padding: 10px;
        }
        h2{
            margin: 10px 0px;
            text-align: center;
        }
        h3{
            margin: 10px  0px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>学员调查问卷</h2>
        <form action="./apply" method="post">
        <h3>您的姓名</h3>
        <input name="name" class="text"  style="width: 150px">
        <h3>您正在学习的技术方向</h3>
        <select name="course" style="width: 150px">
            <option value="java">Java</option>
            <option value="h5">HTML5</option>
            <option value="python">Python</option>
            <option value="php">PHP</option>
        </select>
        <div>
<!--复选框用数组 或 ArrayList接收-->
            <h3>您的学习目的:</h3>
            <input type="checkbox" name="purpose" value="1">就业找工作
            <input type="checkbox" name="purpose" value="2">工作要求
            <input type="checkbox" name="purpose" value="3">兴趣爱好
            <input type="checkbox" name="purpose" value="4">其他
        </div>
        <div style="text-align: center;padding-top:10px" >
            <input type="submit" value="提交" style="width:100px">
        </div>
        </form>

    </div>
</body>
</html>

如果要接收复合数据 直接用数组接收是最简单的 但使用起来并不轻松
可以使用List来接收 但是前方记得要有@RequestParam
更加高级的可以封装成一个对象 用List接收复合数据
Map只能接收单个数据 接收复合数据的时候会造成数据丢失

com/imooc/springmvc/controller/FormController.java
package com.imooc.springmvc.controller;

import com.imooc.springmvc.entity.Form;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;

@Controller
public class FormController {
//    @PostMapping("/apply")  ★★★第一次尝试★★★
    @ResponseBody
//    如果在请求中不包含默认值 => 取别名  默认值机制:ANON匿名
    public String apply(@RequestParam(value = "n", defaultValue = "ANON") String name, String course, Integer[] purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p:purpose){
            System.out.println(p);
        }
        return "SUCCESS";
    }

//    @PostMapping("/apply")  ★★★第二次尝试★★★
    // 请求中的复合数据要被转换为List进行存储
    @ResponseBody
    public String apply(String name, String course, @RequestParam List<Integer> purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p:purpose){
            System.out.println(p);
        }
        return "SUCCESS";
    }

    @PostMapping("/apply")  
    //★★★★★ 推荐使用 ★★★★★
    // 实体类+List 极大简化表单工作量  
    // 请求中的复合数据要被转换为List进行存储
    @ResponseBody
    public String apply(Form form){
        return "SUCCESS";
    }
    //不推荐直接使用Map导入数据 因为复合数据[数组数据]会丢失
}
com/imooc/springmvc/entity/Form.java
public class Form {
    private String name;
    private String course;
    private List<Integer> purpose;
}Setter + Getter

关联对象赋值

复杂内容表单
用户名: <input name="username">
密码:<input name="password">
--------------------------------
姓名:<input name="name">
身份证号:<input name="idno">
过期时间:<input name="expire">
面向对象设计 【关联对象赋值】
public class User{
    private String username;
    private String password;
    private IDcard idcard = new IDCard();
    //Getter + Setter
}
★★ 关联上IDcard ★★
public class IDcard{
    private String name;
    private String idno;
    private Date expire;
    //Getter + Setter
}
用户名: <input name="username">
密码:<input name="password">
--------------------------------
姓名:<input name="idcard.name">
身份证号:<input name="idcard.idno">
过期时间:<input name="idcard.expire">
src/main/webapp/form.html
 <div>
            <h3>您的学习目的:</h3>
            <input type="checkbox" name="purpose" value="1">就业找工作
            <input type="checkbox" name="purpose" value="2">工作要求
            <input type="checkbox" name="purpose" value="3">兴趣爱好
            <input type="checkbox" name="purpose" value="4">其他
        </div>
            <h3>收货人</h3>
<!--  private Delivery delivery = new Delivery() -->
            <input name="delivery.name" class="text" style="width: 150px">
            <h3>联系电话</h3>
            <input name="delivery.mobile" class="text" style="width: 150px">
            <h3>收货地址</h3>
            <input name="delivery.address" class="text" style="width: 150px">

        <div style="text-align: center;padding-top:10px" >
            <input type="submit" value="提交" style="width:100px">
        </div>
com/imooc/springmvc/entity/Form.java
public class Form {
    private String name;
    private String course;
    private List<Integer> purpose;
    private Delivery delivery = new Delivery();
} Getter + Setter
com/imooc/springmvc/entity/Delivery.java
public class Delivery {
    private String name;
    private String address;
    private String mobile;
} Getter + Setter
com/imooc/springmvc/controller/FormController.java
package com.imooc.springmvc.controller;

import com.imooc.springmvc.entity.Form;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;

@Controller
public class FormController {
//    @PostMapping("/apply")  第一次尝试
    @ResponseBody
//    如果在请求中不包含默认值 => 取别名  默认值机制:ANON匿名
    public String apply(@RequestParam(value = "n", defaultValue = "ANON") String name, String course, Integer[] purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p:purpose){
            System.out.println(p);
        }
        return "SUCCESS";
    }

//    @PostMapping("/apply")  第二次尝试
    // 请求中的复合数据要被转换为List进行存储
    @ResponseBody
    public String apply(String name, String course, @RequestParam List<Integer> purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p:purpose){
            System.out.println(p);
        }
        return "SUCCESS";
    }

//    @PostMapping("/apply") 第三次尝试[这个可以 但有更好]
    // 实体类+List 极大简化表单工作量
    // 请求中的复合数据要被转换为List进行存储
    @ResponseBody
    public String apply(Form form){
        return "SUCCESS";
    }

    @PostMapping("/apply")
    @ResponseBody
    public String applyDelivery(Form form){
        System.out.println(form.getDelivery().getName());
        return "SUCCESS";
    }
}

日期类型转换

①注解方法

com/imooc/springmvc/controller/URLMappingController.java
@Controller
@RequestMapping("/um") //类上直接使用默认访问前缀 全局通用请求映射
public class URLMappingController {
@PostMapping("/p1")
    @ResponseBody
    public String postMapping1(User user, String username, @DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime){
        System.out.println(user.getUsername() + ":" + user.getPassword());
        return "This is post method";
    }
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/um/p1" method="post">
        <input name="username"><br/>
        <input name="password"><br/>
        <input name="createTime"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>


②实体对象接收数据

com/imooc/springmvc/entity/User.java
//标准的java bean
public class User {
    private String username;
    private Long password;
// SpringMVC会自动按照这个类型进行转换
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createTime;
} Getter + Setter

③自定义转换器:全局的默认时间转换器

com/imooc/springmvc/converter/MyDateConverter.java
package com.imooc.springmvc.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyDateConverter implements Converter<String, Date> {
    public Date convert(String s) { //转换工作
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date d = sdf.parse(s);
            return d;
        } catch (ParseException e) {
            return null;
        }
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- context:component-scan标签作用 在Spring IoC初始化过程中, 自动创建并管理com.imooc.springmvc
       及子包中拥有以下注解的对象:
           @Repository 通常存放在Dao类上 通常都是与数据发生直接交互的类
           @Service     ...Service类上 业务逻辑类
           @Controller 描述SpringMVC的控制器类
           @Component  无法确定的类型种类
       -->
    <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
    <!--    启用Spring MVC的注解开发模式 让底下的转换类生效-->
    <mvc:annotation-driven conversion-service="conversionService"/>
    <!--    将图片/JS/CSS等静态资源排除在外, 可提高执行效率-->
    <mvc:default-servlet-handler/>
    <!--    通知SpringMVC有哪些转换类-->
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <!--    这是转换类定义的地方-->
        <property name="converters">
            <set>
                <bean class="com.imooc.springmvc.converter.MyDateConverter"/>
            </set>
        </property>
    </bean>
</beans>

解决中文乱码问题

Web应用的中文乱码由来
  • Tomcat默认使用字符集ISO-8859-1,属于西欧字符集
  • 解决乱码的核心思路是将ISO-8859-1转换为UTF-8
  • Controller中请求与响应都需要设置UTF-8字符集
中文乱码的配置
  • Get请求乱码 - server.xml增加URIEncoding属性

去Tomcat-conf文件中寻找server.xml进行增加

 <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="UTF-8"
               maxParameterCount="1000"
               />

===============================================
http://localhost/um/g?manager_name=丽丽
控制台:managerName:丽丽
  • Post请求乱码 - web.xml配置CharacterEncodingFilter
http://localhost/
输入:
张三  123456 2001-03-02
随后点击提交
控制台:??????:123456
src/main/webapp/WEB-INF/web.xml
<filter>
        <filter-name>characterFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
http://localhost/
输入:
张三  123456 2001-03-02
随后点击提交
控制台:张三:123456
  • Response相应乱码 - Spring配置StringHttpMessageConverter

解决响应中的中文乱码

com/imooc/springmvc/controller/URLMappingController.java
package com.imooc.springmvc.controller;

import com.imooc.springmvc.entity.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@Controller
@RequestMapping("/um") //类上直接使用默认访问前缀 全局通用请求映射
public class URLMappingController {
    @GetMapping("/g")
    @ResponseBody
    public String getMapping(@RequestParam("manager_name") String managerName) {
        System.out.println("managerName:"+managerName);
        return "This is get method";
    }

    //    直接访问会出错 post请求如何访问 用进行html表单提交
    @PostMapping("/p")
    @ResponseBody
    public String postMapping(String username, Long password) {
        System.out.println(username + ":" + password);
        return "This is post method";
    }

//    @PostMapping("/p1")
    @ResponseBody
    public String postMapping1(User user){
        System.out.println(user.getUsername() + ":" + user.getPassword());
        return "This is post method";
    }

    @PostMapping("/p1")
    @ResponseBody
    public String postMapping1(User user, String username, @DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime){
        System.out.println(user.getUsername() + ":" + user.getPassword());
        return "这是Post响应";
    }
}
User.java
//标准的java bean
public class User {
    private String username;
    private Long password;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createTime;
}Getter + Setter
com/imooc/springmvc/converter/MyDateConverter.java
package com.imooc.springmvc.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyDateConverter implements Converter<String, Date> {
    public Date convert(String s) { //转换工作
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date d = sdf.parse(s);
            return d;
        } catch (ParseException e) {
            return null;
        }
    }
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/um/p1" method="post">
        <input name="username"><br/>
        <input name="password"><br/>
        <input name="createTime"><br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- context:component-scan标签作用 在Spring IoC初始化过程中, 自动创建并管理com.imooc.springmvc
       及子包中拥有以下注解的对象:
           @Repository 通常存放在Dao类上 通常都是与数据发生直接交互的类
           @Service    ...Service类上 业务逻辑类
           @Controller 描述SpringMVC的控制器类
           @Component  无法确定的类型种类
       -->
    <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
    <!--    启用Spring MVC的注解开发模式 让底下的转换类生效-->
    <mvc:annotation-driven conversion-service="conversionService">
<!--        设置消息转换器-->

        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
<!--                转换什么呢?-->
                <property name="supportedMediaTypes">
                    <list>
<!--  在servlet中是直接  response.setContentType("text/html;charset=utf-8")  -->
                        <value>text/html;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>

    </mvc:annotation-driven>
    <!--    将图片/JS/CSS等静态资源排除在外, 可提高执行效率-->
    <mvc:default-servlet-handler/>
    <!--    通知SpringMVC有哪些转换类-->
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <!--    这是转换类定义的地方-->
        <property name="converters">
            <set>
                <bean class="com.imooc.springmvc.converter.MyDateConverter"/>
            </set>
        </property>
    </bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--    DispatchServlet 对所有请求进行拦截  -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <!--        DispatcherServlet是Spring MVC是最核心的对象
                    DispatcherServlet用于拦截http请求
                    并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理
        -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--        在Web应用启动时自动创建Spring IoC容器,并初始化DispatcherServlet -->
        <!--        applicationContext.xml 初始化参数 启动时候加载-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--        "/" 所有请求都要拦截-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>characterFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

响应中产生结果 [ModelAndView => MVC高效解耦理念]

  • @ResponseBody - 产生响应文本

    • @ResponseBody直接产生响应体数据, 过程不涉及任何视图
    • @ResponseBody可产生标准字符串/JSON/XML等格式数据
    • @ResponseBody被StringHttpMessageConverter所影响
  • ModelAndView - 利用模板引擎渲染输出

    • ModelAndView对象是指”模型(数据)与视图(界面)”对象
    • 通过ModelAndView可将包含数据对象与模板引擎进行绑定
    • SpringMVC中默认的View是JSP, 也可以配置其他模板引擎

提问:为什么不直接访问view.jsp 而是绕了一个圈子还是同样的效果
回答:因为jsp页面是写死的如何将页面和数据绑定在一起呢?先通过访问/um/view这个controller让其方法中产生数据, 之后再将这个数据通过modelandview对象绑定到页面中才可以做到

从请求传来一个用户编号把数据查询出来得到一个user用户对象 然后再view.jsp中把刚刚查询的对象进行显示[动态的]

高效解耦
后端:com/imooc/springmvc/controller/URLMappingController.java
// http://localhost/um/view?userId=1 数据动态查询产生
    @GetMapping("/view")
    public ModelAndView showView(Integer userId){
        ModelAndView mav = new ModelAndView("/view.jsp");
        User user = new User();
        if (userId == 1){
            user.setUsername("lily");
        }else if (userId == 2){
            user.setUsername("smith");
        }
    //  在当前请求中增加一个对象 数据绑定view.jsp
        mav.addObject("u", user);
        return mav;
    }
前端:src/main/webapp/view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>I'm view page</h1>
<hr>
<h2>Username:${u.username}</h2>
</body>
</html>

ModelAndView [重定向]

[SpringMVC若跳转页面需要使用此对象进行数据绑定]
  • mav.addObject()方法设置的属性默认存放在当前请求中
  • 默认ModelAndView使用请求转发(forward)至页面
  • 重定向使用 new ModelAndView(“redirect:/index.jsp”) 新请求

页面重定向使用时机:内部的controller处理逻辑和跳转页面之间没有直接关系时可以用”redirect:/view.jsp”

ModelAndView mav = new ModelAndView("redirect:/view.jsp");
直接访问到了jsp  http://localhost/view.jsp

访问:http://localhost/um/view?userId=1
建立一个 没有斜杠的相对路径 src/main/webapp/um/view.jsp
com/imooc/springmvc/controller/URLMappingController.java
 // http://localhost/um/view?userId=1 数据动态查询产生
    @GetMapping("/view")
    public ModelAndView showView(Integer userId){
//        ModelAndView mav = new ModelAndView("redirect:/view.jsp");
        ModelAndView mav = new ModelAndView();
        mav.setViewName("view.jsp");
//        没有斜杠是代表相对路径 相对于@RequestMapping("/um")地址
//        mav.setViewName("view.jsp");

        User user = new User();
        if (userId == 1){
            user.setUsername("lily");
        }else if (userId == 2){
            user.setUsername("smith");
        }
//      在当前请求中增加一个对象 数据绑定view.jsp
        mav.addObject("u", user);
        return mav;
    }
String与ModelMap实现ModelAndView类似功能 [工作中的小技巧]
com/imooc/springmvc/controller/URLMappingController.java
//   String 与 ModelMap[为模型数据] 返回字符串
//    Controller方法返回String的情况
//    1.方法被@ResponseBody描述, 则SpringMVC直接响应Spring字符串本身
//    2.方法不存在@ResponseBody, 则SpringMVC处理String指代的视图(页面)
// 这里的String是直接代替了@ResponseBody - 产生响应文本
    public String showView1(Integer userId, ModelMap modelMap){
        String view = "/um/view.jsp";
        User user = new User();
        if (userId == 1){
            user.setUsername("lily");
        }else if (userId == 2){
            user.setUsername("smith");
        }
        modelMap.addAttribute("u", user);
        return view;
    }

SpringMVC整合Freemarker [项目:first-springmvc]

① pom.xml引入依赖
要随时导入包 tomcat → Edit configuration → Deployment → Artifacts 将右侧包put in
<dependency>
  <groupId>org.freemarker</groupId>
  <artifactId>freemarker</artifactId>
  <version>2.3.28</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>5.1.9.RELEASE</version>
</dependency>
② 启用Freemarker模板引擎
applicationContext.xml
<bean id="ViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
   <!-- 设置响应输出,并解决中文乱码 --> 
<!-- 渲染完成后向客户端浏览器响应式 响应体中使用的字符集编码 -->
   <property name="contentType" value="text/html;charset=utf-8"></property>
   <!-- 指定Freemarker模板文件扩展名 -->
   <property name="suffix" value=".ftl"/>
</bean>
③ 本身配置Freemarker参数
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- 设置模板保存的目录 tomcat无法直接解析freemarker模板引擎 所以新增/WEB-INF/ftl -->
   <property name="templateLoaderPath" value="/WEB-INF/ftl"/>
   <!-- 其他模板引擎设置 -->
   <property name="freemarkerSettings">
     <props>
         <!-- 设置Freemarker脚本与数据渲染时使用的字符集 -->
         <!-- 模板与数据绑定渲染的过程中使用的字符集编码 -->
         <prop key="defaultEncoding">UTF-8</prop>
     </props> 
   </property>
</bean>
src/main/webapp/WEB-INF/ftl/test.ftl
<h1>${u.username}</h1>
com/imooc/springmvc/controller/FreemarkerController.java
package com.imooc.springmvc.controller;

import com.imooc.springmvc.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
// http://localhost/fm/test  显示 andy
@Controller
@RequestMapping("/fm")
public class FreemarkerController {
    @GetMapping("/test")
    public ModelAndView showTest(){
//        因为之前配置所有配置扩展名是.ftl 所以下面不用写
        ModelAndView mav = new ModelAndView("/test");
        User user = new User();
        user.setUsername("andy");
        mav.addObject("u",user);
        return mav;
    }
}
=====================================================
// http://localhost/fm/test  显示 andy

RESTful开发风格

RESTful 风格(详细介绍 + 案例实现)_c# restful风格接口-CSDN博客

REST与RESTful

  • REST- 表现层状态转换, 资源在网络中以某种表现形式进行状态转移
  • RESTful是基于REST理念的一套开发风格, 是具体的开发规则

RESTful开发规范

URL中所有的都是名词 请求都有不同的含义 返回的数据是JSON或者XML格式

  • 使用URL作为用户交互入口
  • 明确的语义规范(GET, POST, PUT, DELETE)
  • 只返回数据(JSON, XML)不包含任何展现
注解 作用
@RestController 由 @Controller + @ResponseBody组成(返回 JSON 数据格式)
@PathVariable URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到控制器处理方法的形参中
@RequestMapping 注解用于请求地址的解析,是最常用的一种注解
@GetMapping 查询请求
@PostMapping 添加请求
@PutMapping 更新请求
@DeleteMapping 删除请求
@RequestParam 将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

RESTful命名要求

URI 说明 修改建议
GET /articles?au=lily 正确用法
GET /a/1 URI必须具有语义 GET /student/1
POST /createArticle/1 URI必须使用名词 POST /article/1
GET /articles/author/1 URI扁平化, 不超两级 GET /articles/author?id=1
DELETE/articles/1 URI名词区分单复数 GET /articles?au=lily
DELETE /article/1

开发第一个RESTful应用

404究极报错
项目生成文件out/artifacts/XXX/WEB-INF中没有导入lib包
没有将下面的web文件里的web.xml手动复制进去

pom.xml 
写完pom.xml后记得在tomcat配置中的Deployment中加入依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>restful</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
</project>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.imooc.restful"/>
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <!-- response.setContentType("text/html;charset=utf-8") -->
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/>
</beans>
web/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>characterFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
com/imooc/restful/controller/RestfulController.java
package com.imooc.restful.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/restful")
public class RestfulController {
    @GetMapping("/request")
    @ResponseBody
    public String doGetRequest(){
//  双引号中如果包括双引号 要用转义字符 \"
        return "{\"message\":\"返回查询结果\"}";
    }
}

=======================================================
http://localhost/restful/request
{"message":"返回查询结果"}

实现RESTful实验室

com/imooc/restful/controller/RestfulController.java
package com.imooc.restful.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/restful")
public class RestfulController {
    @GetMapping("/request")
    @ResponseBody
    public String doGetRequest(){
//  双引号中如果包括双引号 要用转义字符 \"
        return "{\"message\":\"返回查询结果\"}";
    }
    @PostMapping("/request")
    @ResponseBody
    public String doPostRequest(){
        return "{\"message\":\"数据新建成功\"}";
    }
    @PutMapping ("/request")
    @ResponseBody
    public String doPutRequest(){
        return "{\"message\":\"数据更新成功\"}";
    }
    @DeleteMapping("/request")
    @ResponseBody
    public String doDeleteRequest(){
        return "{\"message\":\"数据删除成功\"}";
    }
}
===========================
http://localhost/client.html
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.imooc.restful"/>
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <!-- response.setContentType("text/html;charset=utf-8") -->
                        <value>text/html;charset=utf-8</value>
                        <!--只要响应产生就会使用utf-8字符集-->
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>characterFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>restful</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.9</version>
        </dependency>
    </dependencies>
</project>
web/client.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.3.1.min.js"></script>
    <script>
        $(function () {
            $("#btnGet").click(function () {
                $.ajax({
                    url: "/restful/request",
                    type: "get",
                    dataType: "json",
                    success: function (json) {
                        $("#message").text(json.message);
                    }
                })
            });
        })
        $(function () {
            $("#btnPost").click(function () {
                $.ajax({
                    url: "/restful/request",
                    type: "post",
                    dataType: "json",
                    success: function (json) {
                        $("#message").text(json.message);
                    }
                })
            });
        })
        $(function () {
            $("#btnPut").click(function () {
                $.ajax({
                    url: "/restful/request",
                    type: "put",
                    dataType: "json",
                    success: function (json) {
                        $("#message").text(json.message);
                    }
                })
            });
        })
        $(function () {
            $("#btnDelete").click(function () {
                $.ajax({
                    url: "/restful/request",
                    type: "delete",
                    dataType: "json",
                    success: function (json) {
                        $("#message").text(json.message);
                    }
                })
            });
        })
    </script>
</head>
<body>
    <input type="button" id="btnGet" value="发送Get请求">
    <input type="button" id="btnPost" value="发送Post请求">
    <input type="button" id="btnPut" value="发送Put请求">
    <input type="button" id="btnDelete" value="发送Delete请求">
    <h1 id="message"></h1>
</body>
</html>

RestController注解与路径变量

在post请求中通过client.html利用Ajax动态注入了一个rid到RestfulController.java
代替了所有的@ResponseBody 默认向请求台进行输出

@Controller: 用于标识一个类是Spring MVC中的控制器,类似于标准的@Controller 注解。它告诉Spring框架该类是一个控制器,可以处理HTTP请求。

@ResponseBody: 用于将方法的返回值直接作为HTTP响应的主体(Body)内容。这意味着不会进行视图解析,而是直接将返回的对象(通常是JSON或XML)写入HTTP响应

因此,@RestController 的作用是将一个控制器类标记为RESTful风格的控制器,其中的每个方法都被视为返回数据,而不是视图。这样就不需要在每个方法上都添加 @ResponseBody 注解,因为该注解已经包含在 @RestController 中。
client.html
 $(function(){
            $("#btnPost").click(function () {
                $.ajax({
                    url : "/restful/request/100",
                    type : "post" ,
                    dataType : "json" ,
                    success : function(json){
                   $("#message").text(json.message+":"+json.id);
                    }
                })
            });
        })
com/imooc/restful/controller/RestfulController.java
    
package com.imooc.restful.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/restful")
//@CrossOrigin(origins = {"http://localhost:8080","http://www.imooc.com"})
//@CrossOrigin(origins = "*",maxAge = 3600)
public class RestfulController {
    @GetMapping("/request")
    //@ResponseBody
    public String doGetRequest() {
        return "{\"message\":\"返回查询结果\"}";
    }

    // POST /article/1
    // POST /restful/request/100
    @PostMapping("/request/{rid}")
    //@ResponseBody
    public String doPostRequest(@PathVariable("rid") Integer requestId) {
        return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
    }

    @PutMapping("/request")
    //@ResponseBody
    public String doPutRequest() {
        return "{\"message\":\"数据更新成功\"}";
    }

    @DeleteMapping("/request")
    //@ResponseBody
    public String doDeleteRequest() {
        return "{\"message\":\"数据删除成功\"}";
    }
}

简单请求与非简单请求

  • 简单请求是指标准结构的HTTP请求, 对应GET/POST请求
  • 非简单请求是复杂要求的HTTP请求, 指PUT/DELETE、扩展标准请求
  • 两者最大区别是非简单请求发送前需要发送预检请求 [看看能不能进行处理 可以才实际处理 预先处理不符合的数据挡在外面]
web.xml
<!--    对put和delete请求进行支持-->
    <filter>
        <filter-name>formContentFilter</filter-name>
        <filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>formContentFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
web/client.html
$(function(){
            $("#btnPost").click(function () {
                $.ajax({
                    url : "/restful/request/100",
                    type : "post" ,
                    data : "name=lily&age=23",
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message+":"+json.id);
                    }
                })
            });
        })

        $(function(){
            $("#btnPut").click(function () {
                $.ajax({
                    url : "/restful/request",
                    type : "put" ,
                    data : "name=lily&age=23",
                    dataType : "json" ,
                    success : function(json){
                        $("#message").text(json.message);
                    }
                })
            });
        })
com/imooc/restful/controller/RestfulController.java
 // POST /article/1
    // POST /restful/request/100
    @PostMapping("/request/{rid}")
    //@ResponseBody
    public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
    }

    @PutMapping("/request")
    //@ResponseBody
    public String doPutRequest(Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据更新成功\"}";
    }
====================
lily:23

创建一个Person用来插入数据 并打印在控制台

com/imooc/restful/entity/Person.java
public class Person {
    private String name;
    private Integer age;
}Getter + Setter

JSON序列化

pom.xml
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.9</version>
        </dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.9</version>
        </dependency>
一定要使用2.9以后的版本 之前的版本有严重的安全风险
http://localhost/restful/person?id=1
@GetMapping("/person")
//不返回String 返回JSON序列化的对象
    public Person findByPersonId(Integer id){
        Person p = new Person();
        if (id==1){
            p.setName("lily");
            p.setAge(23);
        } else if (id==2) {
            p.setName("smith");
            p.setAge(22);
        }
        return p;
    }
============================================
{
    "name": "lily",
    "age": 23
}



http://localhost/restful/persons
@GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();
        Person p1 = new Person();
        p1.setName("lily");
        p1.setAge(23);
        list.add(p1);

        Person p2 = new Person();
        p2.setName("smith");
        p2.setAge(22);
        list.add(p2);
        
        return list;
    }
=============================================
[
    {
        "name": "lily",
        "age": 23
    },
    {
        "name": "smith",
        "age": 22
    }
]

服务器返回json数组

client.html
$(function(){
            $("#btnPersons").click(function () {
                $.ajax({
                    url : "/restful/persons",
                    type : "get" ,
                    dataType : "json" ,
                    success : function(json){
                        console.info(json);
                        for (var i=0; i<json.length; i++){
                            var p = json[i];
                            $("#divPersons").append("<h2>" + p.name + "-" + p.age + "</h2>")
                        }
                    }
                })
            });
        })

    </script>
</head>
<body>
    <input type="button" id="btnGet" value="发送Get请求">
    <input type="button" id="btnPost" value="发送Post请求">
    <input type="button" id="btnPut" value="发送Put请求">
    <input type="button" id="btnDelete" value="发送Delete请求">
    <h1 id="message"></h1>
    <hr/>
    <!-- 点击btnPersons按钮时发送Ajax请求[上面有操作代码] 将所有人员信息追加到div中-->
    <input type="button" id="btnPersons" value="查询所有人员">
    <div id="divPersons"></div>
</body>
com/imooc/restful/controller/RestfulController.java
@GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();
        Person p1 = new Person();
        p1.setName("lily");
        p1.setAge(23);
        list.add(p1);

        Person p2 = new Person();
        p2.setName("smith");
        p2.setAge(22);
        list.add(p2);

        return list;
    }

===============================================
http://localhost/client.html
点击查询所有人员
=> 
lily-23
smith-22

增加时间!

Person.java
public class Person {
    private String name;
    private Integer age;
// 记得用日期的格式化输出
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date birthday;
}Getter + Setter
client.html
$(function(){
            $("#btnPersons").click(function () {
                $.ajax({
                    url : "/restful/persons",
                    type : "get" ,
                    dataType : "json" ,
                    success : function(json){
                        console.info(json);
                        for (var i=0; i<json.length; i++){
                            var p = json[i];
                            $("#divPersons").append("<h2>" + p.name + "-" + p.age + "-" + p.birthday+ "</h2>")
                        }
                    }
                })
            });
        })

===================================
lily-23-2024-01-17 09:09:28
smith-22-2024-01-17 09:09:28

浏览器的同源策略

  • 同源策略阻止从一个域加载的脚本去获取另一个域上的资源
  • 只要协议域名端口有任何一个不同,都被当做是不同的域
  • 浏览器Console看到Access-Control-Allow-Origin就代表了跨域了

HTML中允许跨域的标签

  • < img > - 显示远程图片
  • < script > - 加载远程JS
  • < link > - 加载远程CSS

CORS跨域资源访问

  • CORS是一种机制, 使用额外的HTTP头通知浏览器可以访问其他域
  • URL响应头包含 Access-Control-* 指明请求允许跨域

Spring MVC解决跨域访问

  • @CrossOrigin - Controller跨域注解
  • < mvc:cors > - Spring MVC全局跨域配置
RestfulController.java [代码第四行]
@RestController
@RequestMapping("/restful")
//@CrossOrigin(origins = {"http://localhost:8080","http://www.imooc.com"})
//@CrossOrigin(origins = "*",maxAge = 3600) 所有端口都会访问发送请求
//maxAge = 3600 一小时时间后发送预检请求  之内的就发送实际请求 【非简单请求】
public class RestfulController {
    @GetMapping("/request")
    //@ResponseBody
    public String doGetRequest(){
        return "{\"message\":\"返回查询结果\"}";
    }

    // POST /article/1
    // POST /restful/request/100
    @PostMapping("/request/{rid}")
    //@ResponseBody
    public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
    }

    @PutMapping("/request")
    //@ResponseBody
    public String doPutRequest(Person person){
        System.out.println(person.getName() + ":" + person.getAge());
        return "{\"message\":\"数据更新成功\"}";
    }

    @DeleteMapping("/request")
    //@ResponseBody
    public String doDeleteRequest(){
        return "{\"message\":\"数据删除成功\"}";
    }

    @GetMapping("/person")
    public Person findByPersonId(Integer id){
        Person p = new Person();
        if (id==1){
            p.setName("lily");
            p.setAge(23);
        } else if (id==2) {
            p.setName("smith");
            p.setAge(22);
        }
        return p;
    }

    @GetMapping("/persons")
    public List<Person> findPersons(){
        List list = new ArrayList();
        Person p1 = new Person();
        p1.setName("lily");
        p1.setAge(23);
        p1.setBirthday(new Date());
        list.add(p1);

        Person p2 = new Person();
        p2.setName("smith");
        p2.setAge(22);
        p2.setBirthday(new Date());
        list.add(p2);
        return list;
    }
}

CORS全局配置

<mvc:cors>
    <mvc:mapping path="/restful/**"
        allowed-origins="http://localhost:8080,http://www.imooc.com"
        max-age="3600"/>
</mvc:cors>

SpringMVC拦截器 [高级组件]

拦截器-Interceptor
  • 拦截器(Interceptor)用于对URL请求进行前置/后置过滤
  • Interceptor与Filter用途相似, 但实现方式不同
  • Interceptor底层基于Spring AOP面向切面编程实现[类似于环绕通知]
拦截器开发流程
  • Maven依赖servlet-api
  • 实现HandlerInterceptor接口
  • applicationContext配置过滤地址
HandlerInterceptor接口
  • preHandle - 前置执行处理
  • postHandle - 目标资源已被Spring MVC框架处理 没产生响应文本
  • afterCompletion - 相应文本已经产生
pom.xml
     <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
<!-- 只有在开发编译才会引用 打包最终使用的时候会排除在外 因为 servlet-api与tomcat-api冲突  -->
            <scope>provided</scope>
        </dependency>
    </dependencies>
com/imooc/restful/interceptor/MyInterceptor.java
package com.imooc.restful.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {
//    Code -> Implement Methods

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURI()+"准备执行");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(request.getRequestURI()+"目标处理成功");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(request.getRequestURI()+"响应内容已产生");
    }
//  随后去applicationContext.xml配置
}

=============================================================
/准备执行
/目标处理成功
/响应内容已产生
/准备执行
/目标处理成功
/响应内容已产生
/准备执行
/目标处理成功
/响应内容已产生
    
http://localhost/restful/persons
/restful/persons准备执行
/restful/persons目标处理成功
/restful/persons响应内容已产生
    
http://localhost/client.html
/client.html准备执行
/client.html目标处理成功
/client.html响应内容已产生
applicationContext.xml
<mvc:interceptors>
        <mvc:interceptor>
<!--  哪些[所有]地址进行拦截 下面加个bean是哪个类进行处理[送到这个类中处理]-->
            <mvc:mapping path="/**"/>
            <bean class="com.imooc.restful.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

拦截器使用细则

applicationContext.xml
<mvc:interceptors>
        <mvc:interceptor>
<!--            哪些[所有]地址进行拦截 下面加个bean是哪个类进行处理[送到这个java中处理]-->
       <!-- <mvc:mapping path="/**"/> -->
            <mvc:mapping path="/restful/**"/>
            <mvc:mapping path="/webapi/**"/>
<!--            以下是不需要拦截 排除在外的-->
            <mvc:exclude-mapping path="/**.ico"/>
            <mvc:exclude-mapping path="/**.jpg"/>
            <mvc:exclude-mapping path="/**.gif"/>
            <mvc:exclude-mapping path="/**.js"/>
            <mvc:exclude-mapping path="/**.css"/>
<!--   创建一个resources  规范静态文件目录 一次性都排除在外-->
            <mvc:exclude-mapping path="/rescources/**"/>

            <bean class="com.imooc.restful.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURI()+"准备执行");
        return true;
    }
通过preHandle的返回值可以做很多事情
对某一个url进行前置检查 对请求放行 如果不符合要求 直接在拦截器中返回相应
return true; 请求依次向后发送
return false; 请求被阻挡

开发”用户流量”拦截器

pom.xml [引入新的依赖后一定要去Tomcat导入新的依赖包]
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="accessHistoryLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>d:/logs/history.%d.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
    <logger name="com.imooc.restful.interceptor.AccessHistoryInterceptor"
            level="INFO" additivity="false">
        <appender-ref ref="accessHistoryLog"/>
    </logger>
</configuration>
com/imooc/restful/interceptor/AccessHistoryInterceptor.java
package com.imooc.restful.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AccessHistoryInterceptor implements HandlerInterceptor {
    private Logger logger = LoggerFactory.getLogger(AccessHistoryInterceptor.class);
//    前置处理

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        StringBuilder log = new StringBuilder();
        log.append(request.getRemoteAddr());
        log.append("|");
        log.append(request.getRequestURL());
        log.append("|");
        log.append(request.getHeader("user-agent"));
        logger.info(log.toString());
        return true;
    }
}
com/imooc/restful/interceptor/MyInterceptor.java
package com.imooc.restful.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {
//    Code -> Implement Methods

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getRequestURL() + "-准备执行");
//        response.getWriter().print("[]");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(request.getRequestURL() + "-目标处理成功");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(request.getRequestURL() + "-响应内容已产生");
    }
}

==============================================================
http://localhost/client.html
[http-nio-80-exec-1] 2024-01-18 10:43:54,739 DEBUG o.s.w.s.DispatcherServlet - GET "/login.html", parameters={}
[http-nio-80-exec-1] 2024-01-18 10:43:54,750 DEBUG o.s.w.s.h.SimpleUrlHandlerMapping - Mapped to org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@5c0e8bb9
[http-nio-80-exec-1] 2024-01-18 10:43:54,758 DEBUG o.s.w.s.DispatcherServlet - Completed 404 NOT_FOUND
[http-nio-80-exec-2] 2024-01-18 10:44:00,495 DEBUG o.s.w.s.DispatcherServlet - GET "/restful/request", parameters={}
[http-nio-80-exec-2] 2024-01-18 10:44:00,502 DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to public java.lang.String com.imooc.restful.controller.RestfulController.doGetRequest()
http://localhost/restful/request-准备执行
[http-nio-80-exec-2] 2024-01-18 10:44:00,539 DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Using 'application/json;charset=utf-8', given [application/json, text/javascript, */*;q=0.01] and supported [text/html;charset=utf-8, application/json;charset=utf-8, text/plain, */*, application/json, application/*+json]
[http-nio-80-exec-2] 2024-01-18 10:44:00,539 DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Writing ["{"message":"返回查询结果"}"]
http://localhost/restful/request-目标处理成功
http://localhost/restful/request-响应内容已产生
[http-nio-80-exec-2] 2024-01-18 10:44:00,580 DEBUG o.s.w.s.DispatcherServlet - Completed 200 OK
[http-nio-80-exec-5] 2024-01-18 10:44:08,194 DEBUG o.s.w.s.DispatcherServlet - GET "/restful/persons", parameters={}
[http-nio-80-exec-5] 2024-01-18 10:44:08,195 DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to public java.util.List<com.imooc.restful.entity.Person> com.imooc.restful.controller.RestfulController.findPersons()
http://localhost/restful/persons-准备执行
RestfulController.findPersons() - return list
[http-nio-80-exec-5] 2024-01-18 10:44:08,211 DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Using 'application/json', given [application/json, text/javascript, */*;q=0.01] and supported [application/json, application/*+json]
[http-nio-80-exec-5] 2024-01-18 10:44:08,211 DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Writing [[com.imooc.restful.entity.Person@619c4d80, com.imooc.restful.entity.Person@71459fb]]
http://localhost/restful/persons-目标处理成功
http://localhost/restful/persons-响应内容已产生
===================================================================
D:\logs\history.2024-01-18
[http-nio-80-exec-1] 2024-01-18 10:43:54,755 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/login.html|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
[http-nio-80-exec-2] 2024-01-18 10:44:00,502 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
[http-nio-80-exec-5] 2024-01-18 10:44:08,195 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/persons|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
[http-nio-80-exec-3] 2024-01-18 10:44:10,137 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request/100|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
[http-nio-80-exec-4] 2024-01-18 10:44:10,935 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
[http-nio-80-exec-6] 2024-01-18 10:44:11,481 INFO c.i.r.i.AccessHistoryInterceptor - 0:0:0:0:0:0:0:1|http://localhost/restful/request|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
applicationContext.xml
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/resources/**"/>
            <bean class="com.imooc.restful.interceptor.AccessHistoryInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

Spring MVC处理流程

阅读全文

Spring

2023/12/9

Spring

Spring IoC

内容 说明 重要程度
Spring框架介绍 Spring IoC、DI和AOP等核心概念 ★★★★★★
Spring IoC容器 Spring实例化与管理对象 ★★★★★★
集合对象注入 注入List、Set、Map集合对象 ★★★★★★
底层原理 Spring Bean的生命周期 ★★★★★★
注解与Java Config Spring注解分类和常用注解应用 ★★★★★★

Spring AOP

不修改源代码的程序扩展
内容 说明 重要程度
理解AOP及名词 Spring AOP开发与配置流程 ★★★★★★(面试)
五种通知类型 Spring五种通知类型与应用场景 ★★★
切点表达式 PointCut切点表达式的语法规则及应用 ★★
代理模式 JDK动态代理和CGLib代理的执行过程 ★★★★★★(面试)

Spring JDBC与声明式事务

JDBC的扩展

内容 说明 重要程度
Spring JDBC Spring JDBC的环境配置 ★★★★
RestTemplate 基于RestTemplate实现SQL处理 ★★★
配置声明式事务 声明式事务的配置过程 ★★★★★★
事务传播行为介绍 讲解常用事务传播行为的用途 ★★★
声明式事务注解形式 基于注解使用声明式事务 ★★★★★★

★★★★★★  Spring  ★★★★★★

IoC容器负责实例化配置组装对象。 IoC容器从XML文件获取信息并相应地工作。

IoC容器执行的主要任务是:

  • 实例化应用程序类
  • 配置对象
  • 组装对象之间的依赖关系

有两种类型的IoC容器

  • BeanFactory
  • ApplicationContext

IoC控制反转

对象的控制权交给第三方进行管理(中间人)
  • IoC控制反转,全称Inverse of Control,是一种设计理念
  • 由代理人来创建与管理对象,消费者通过代理人来获取对象
  • IoC的目的是降低对象之间直接耦合[更好适合对象之间的变化]
  • 加入IoC容器将对象统一管理,让对象关联变为弱耦合

顾客 ←(快递) 果商冷冻仓库 (批发)→ 苹果

DI依赖注入

  • IoC是设计理念,是现代程序设计遵循的标准,是宏观目标
  • DI(Dependency Injection)是具体技术实现,是微观实现
  • DI在Java中利用反射技术实现对象注入(Injection) [不同语言运用不同技术]

Spring含义

  • Spring可以从广义和狭义两个角度看待
  • 广义的Spring是指Spring生态系统
  • 狭义的Spring是指Spring框架(Spring Framework)
广义的Spring生态体系
  • 分布式微服务 SpringCloud
  • Reactive相应服务
  • Web apps 是 SpringMVC 中的
  • Serverless 无服务器内容
  • Event Driven 和 Batch
  • Spring Framework Spring Boot Spring Cloud Spring Cloud Data Flow…
狭义的Spring框架
  • Spring框架是企业开发复杂性的一站式解决方案
  • Spring框架的核心是IoC容器AOP面向切面编程
  • Spring IoC负责创建与管理系统对象,并在此基础上扩展功能(不修改源代码)
传统开发方式
  • 对象直接引用导致对象硬性关联,程序难以扩展维护 new B new A
Spring IoC容器
  • IoC容器是Spring生态的地基,用于统一创建于管理对象依赖

    使用者直接提取Spring IoC容器中的已经将依赖ObjectB注入到ObjectA 直接提取A

Spring IoC容器职责 [宏观理念]
  • 对象的控制权交由第三方统一管理 (IoC控制反转)
  • 利用Java反射技术实现运行时对象创建与关联 (DI依赖注入) [技术实现]
  • 基于配置提高应用程序的可维护性与扩展性

Spring IoC初体验

三个小孩吃三种不同的苹果
原始代码

Apple.java

public class Apple {
    private String title;
    private String color;
    private String origin;
    Construct(空+满) Getter+Setter
}

Child.java

public class Child {
    private String name;
    private Apple apple;
    Construct(空+满) Getter+Setter
    public void eat(){
        System.out.println(name + "吃到了" + apple.getOrigin() + "种植的" + apple.getTitle());
    }
}

Application.java

package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Apple;
import com.imooc.spring.ioc.entity.Child;

public class Application {
    public static void main(String[] args) {
        Apple apple1 = new Apple("红富士", "红色", "欧洲");
        Apple apple2 = new Apple("青苹果", "绿色", "中亚");
        Apple apple3 = new Apple("红富士", "红色", "欧洲");
        Child lily = new Child("莉莉",apple1);
        Child andy = new Child("安迪",apple2);
        Child luna = new Child("露娜",apple3);
        lily.eat();
        andy.eat();
        luna.eat();
    }
}
Spring IoC方式代码[不修改源代码 不用new 引入容器让对象统一管理]

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Spring_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

applicationContext.xml [静态信息可以放其中] **属性**元素的 ref 属性用于定义另一个bean的引用。

SpringIoC核心配置文件 右键resources创建 所有对象的创建以及关联的设置都是在applicationContext.xml这里进行
Spring.io -> Project -> Spring Framework -> LEARN -> Reference Doc. ->  Core

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans> 
并且加入配置Spring[出现在提示页面]

------------------------  更新一次  ------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    在IoC容器启动时,自动由Spring实例化Apple对象,取名sweetApple放入到容器中  -->
     <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="红富士"></property>
            <property name="origin" value="欧洲"></property>
            <property name="color" value="红色"></property>
    </bean>

    <bean id="sourApple" class="com.imooc.spring.ioc.entity.Apple">
        <property name="title" value="青苹果"></property>
        <property name="origin" value="中亚"></property>
        <property name="color" value="绿色"></property>
    </bean>

    <bean id="softApple" class="com.imooc.spring.ioc.entity.Apple">
        <property name="title" value="沙果"></property>
        <property name="origin" value="中国"></property>
        <property name="color" value="黄色"></property>
    </bean>

    <bean id="rdApple" class="com.imooc.spring.ioc.entity.Apple">
        <property name="title" value="蛇果"></property>
        <property name="origin" value="美国"></property>
        <property name="color" value="红色"></property>
    </bean>

    <bean id="lily" class="com.imooc.spring.ioc.entity.Child">
        <property name="name" value="莉莉"/>
        <property name="apple" ref="sweetApple"/>
    </bean>

    <bean id="andy" class="com.imooc.spring.ioc.entity.Child">
        <property name="name" value="安迪"/>
        <property name="apple" ref="sourApple"/>
    </bean>

    <bean id="luna" class="com.imooc.spring.ioc.entity.Child">
        <property name="name" value="露娜"/>
        <property name="apple" ref="rdApple"/>
    </bean>
</beans>

SpringApplication.java

package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Apple;
import com.imooc.spring.ioc.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Apple sweetApple = context.getBean("sweetApple", Apple.class);
        System.out.println(sweetApple.getTitle());
        //从IoC容器中提取beanId=lily的对象
        Child lily = context.getBean("lily", Child.class);
        lily.eat();
        Child andy = context.getBean("andy", Child.class);
        andy.eat();
        Child luna = context.getBean("luna", Child.class);
        luna.eat();
    }
}
————————————————————————————————————————————————————————————————————————————————————
红富士
莉莉吃到了欧洲种植的红富士
安迪吃到了中亚种植的青苹果
露娜吃到了美国种植的蛇果

利用IoC容器有利于对象与对象之间的解耦 springIoC大大提高了程序的维护与延展

XML管理对象(Bean)

  • 基于XML配置Bean
上述的吃苹果例题就是基于xml配置Bean
  • 基于注解配置Bean
  • 基于Java代码配置Bean(java .config)
applicationContext.xml
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
            <property name="title" value="红富士"></property>
            <property name="origin" value="欧洲"></property>
            <property name="color" value="红色"></property>
</bean>
所有都要用bean标签 SpringIoC实例化以后在容器的唯一编号: id="sweetAppele" SpringIoC就知道创建IoC容器时实例化一个Apple对象同时bean id="..."
XML方式创建IoC容器
//创建IoC容器并根据配置文件创建对象
ApplicationContext context = new 
ClassPathXmlApplicationContext("classpath:applicationContext.xml");

实例化Bean的三种方式

  • 基于构造方法对象实例化 (90%以上)
利用构造方法参数名实例化 [推荐]
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<!-- 没有constructor-arg则代表调用默认构造方法实例化 -->
  <constructor-arg name="title" value="红富士"/>
  <constructor-arg name="origin" value="欧洲"/>           
  <constructor-arg name="color" value="红色"/>     
</bean>

Apple.java

public class Apple {
    private String title;
    private String color;
    private String origin;

    public Apple() {
        System.out.println("Apple对象已创建," + this);
    }

    public Apple(String title, String color, String origin) {
        System.out.println("通过带参构造方法创建对象" + this);
        this.title = title;
        this.color = color;
        this.origin = origin;
    }
    ......

SpringApplication.java

package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Apple;
import com.imooc.spring.ioc.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
}
___________________________________________________________________________
Apple对象已创建,com.imooc.spring.ioc.entity.Apple@39fb3ab6
通过带参构造方法创建对象com.imooc.spring.ioc.entity.Apple@1a968a59
利用构造方法参数位置实例化
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<!-- 利用构造方法参数位置实现对象实例化 -->
  <constructor-arg index="0" value="红富士"/>
  <constructor-arg index="1" value="欧洲"/>
  <constructor-arg index="2" value="红色"/>
</bean>
  • 基于静态工厂实例化 (在IoC容器之外通过程序组织对象)

AppleStaticFactory.java
package com.imooc.spring.ioc.factory;

import com.imooc.spring.ioc.entity.Apple;

public class AppleStaticFactory {
    public static Apple createSweetApple(){ //静态工厂 用于创建对象的方法是静态的
        Apple apple = new Apple();
        apple.setTitle("红富士");
        apple.setOrigin("欧洲");
        apple.setColor("红色");
        return apple;
    }
}
applicationContext.java
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--利用静态工厂获取对象-->
    <bean id="apple4" class="com.imooc.spring.ioc.factory.AppleStaticFactory"
          factory-method="createSweetApple"/>
</beans>

<bean id="a" class="com.nhooo.A" factory-method="getA"></bean>
A.java
package com.nhooo;
public class A {
private static final A obj=new A();
private A(){System.out.println("private constructor");}
public static A getA(){
    System.out.println("factory method ");
    return obj;
}
public void msg(){
    System.out.println("hello user");
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="a" class="com.nhooo.A" factory-method="getA"></bean>
</beans>
Test.java
package org.sssit;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    A a=(A)context.getBean("a");
    a.msg();
}
}
=============================================================
private constructor
factory method
hello user
  • 基于工厂实例方法实例化

AppleFactoryInstance.java
package com.imooc.spring.ioc.factory;

import com.imooc.spring.ioc.entity.Apple;

/**
 * 工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的实例方法创建对象的过程
 */
public class AppleFactoryInstance {
    public Apple createSweetApple(){ //静态工厂 用于创建对象的方法是静态的
        Apple apple = new Apple();
        apple.setTitle("红富士");
        apple.setOrigin("欧洲");
        apple.setColor("红色");
        return apple;
    }
}
applicationContext.java
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--利用工厂实例方法获取对象-->
    <bean id="factoryInstance" class="com.imooc.spring.ioc.factory.AppleFactoryInstance"/>
    <bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>
</beans>
SpringApplication.java
public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
}

从IoC容器获取bean

Apple sweetApple = context.getBean("sweetApple",Apple.class); 【推荐】
                    或者
Apple sweetApple = (Apple)context.getBean("sweetApple");
-------------------------------------------------------------
System.out.println(sweetApple.getTitle());
public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Apple apple4 = context.getBean("apple4", Apple.class); 【推荐】
        System.out.println(apple4.getTitle());
        
        Apple apple3 = (Apple)context.getBean("apple3");
        System.out.println(apple3.getTitle());
    }
}
id与name属性相同点
  • bean id 与 name 都是设置对象在IoC容器中唯一标识
<bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
<bean name="apple3" class="com.imooc.spring.ioc.entity.Apple">
  • 两者在同一个配置文件中都不允许出现重复
  • 两者允许在多个配置文件中出现重复,新对象覆盖旧对象
  • id要求更为严格,一次只能定义一个对象标识 【推荐】
  • name更为宽松,一次允许定义多个对象标识
  • tips: id与name的命名要求有意义,按驼峰命名书写
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
        <!-- 利用构造方法参数位置实现对象实例化 -->
        <constructor-arg index="0" value="红富士"/>
        <constructor-arg index="1" value="欧洲"/>
        <constructor-arg index="2" value="红色"/>
        <constructor-arg index="3" value="19.8"/>
    </bean>
</beans>
applicationContext-1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="apple2" class="com.imooc.spring.ioc.entity.Apple">
        <!-- 利用构造方法参数位置实现对象实例化 -->
        <constructor-arg name="title" value="红富士2号"/>
        <constructor-arg name="origin" value="欧洲"/>
        <constructor-arg name="color" value="红色"/>
        <constructor-arg name="price" value="19.8"/>
    </bean>
</beans>
SpringApplication.java
public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml","classpath:applicationContext-1.xml");
        Apple apple2 = context.getBean("apple2",Apple.class);
        System.out.println(apple2.getTitle());
        Apple apple3 = context.getBean("apple3",Apple.class);
        System.out.println(apple3.getTitle());
    }
}
______________________________________________________________________
红富士2号
红富士

< bean name = “apple2, apple7” class=”com.imooc.spring.ioc.entity.Apple” >
如果使用name(不使用id)可以在后面增加标识名
在没有id与name的bean默认使用类名全称作为bean标识
< bean class=”com.imooc.spring.ioc.entity.Apple” >

路径匹配表达式

加载单个配置文件
//创建IoC容器并根据配置文件创建对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
加载多配置文件
String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
路径表达式 (config.xml = ApplicationContext.xml)
表达式实例 说明
classpath:config.xml 扫描classpath根路径(不包含jar)的config.xml
classpath:com/imooc/config.xml 扫描classpath下(不包含jar)com.imooc包中的config.xml
classpath*:com/imooc/config.xml 扫描classpath下(包含jar)com.imooc包中的config.xml
classpath:config-*.xml 扫描classpath根路径下所有以config-开头的XML文件
classpath:com/**/config.xml 扫描com包下(包含任何子包)的config.xml
file:c:/config.xml 扫描c盘根路径config.xml

对象依赖注入

  • 依赖注入是指运行时将容器内对象利用反射赋給其他对象的操作
  • 基于setter方法注入对象
利用setter实现静态数值注入
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<!-- IoC容器自动利用反射机制运行时调用setXXX方法为属性赋值 -->
    <property name="title" value="红富士"/>
    <property name="color" value="红色"/>
    <property name="origin" value="欧洲"/>
    <property name="price" value="19.8"/>
</bean>
利用setter实现对象注入 【核心操作 ref=””

**属性**元素的 ref 属性用于定义另一个bean的引用。

<bean id="lily" class="com.imooc.spring.ioc.entity.Child">
    <property name="name" value="莉莉"/>
    <!-- 利用ref注入依赖对象 -->
    <property name="apple" ref="sweetApple"/>
</bean>
Child.java   #创建好的apple对象赋予給参数进入setApple
public void setApple(Apple apple) {
     System.out.println("注入的Apple对象:" + apple);
     this.apple = apple;
}
SpringApplication.java
public class SpringApplication {
    public static void main(String[] args) {
        //创建Spring IoC容器,并根据配置文件在容器中实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml","classpath:applicationContext-1.xml");
        Apple sweetApple = context.getBean("sweetApple", Apple.class);
        System.out.println(sweetApple.getTitle());
    }
}

体验依赖注入的优势 (高效解耦)

applicatioinContext-dao.xml    #用于数据库的增删改查
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.imooc.spring.ioc.bookshop.dao.BookDaoImpl">
//如果更改了数据库类型 只需要重新创建一个java实现接口BookDao的insert 之后只需更改class="com.imooc.spring.ioc.bookshop.dao.BookDaoOracleImpl"
    </bean>
</beans>
applicatioinContext-service.xml 
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookService" class="com.imooc.spring.ioc.bookshop.service.BookService">
<!--        id=bookDao     -->
       <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>
BookDao.java
package com.imooc.spring.ioc.bookshop.dao;

public interface BookDao {
    public void insert();
}
BookDaoImpl.java
package com.imooc.spring.ioc.bookshop.dao;

public class BookDaoImpl implements BookDao{
    @Override
    public void insert() {
        System.out.println("向mysql book表插入数据");
    }
}
BookService.java
package com.imooc.spring.ioc.bookshop.service;

import com.imooc.spring.ioc.bookshop.dao.BookDao;

public class BookService {
    private BookDao bookDao; //接口将在ioc启动的时候动态注入
    public void purchase(){
        System.out.println("正在执行图书采购业务方法");
        bookDao.insert();
    }
    public BookDao getBookDao(){
        return bookDao;
    }
    public void setBookDao(BookDao bookDao){
        this.bookDao = bookDao;
    }
}
BookShopApplication.java
package com.imooc.spring.ioc.bookshop;

import com.imooc.spring.ioc.bookshop.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BookShopApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.purchase();
    }
}

利用构造方法实现对象依赖注入

对象依赖注入
  • 依赖注入是指运行时将容器内对象利用反射赋給其他对象的操作
  • 基于setter方法注入对象
  • 基于构造方法注入对象
【com.imooc.spring.ioc.entity】
Child.java + Apple.java 

Apple.java中可以加一个
public void apple(){
        System.out.println(origin+"国家" +color+"的"+title+"食物");
    }
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sweetApple" class="com.imooc.Apple.entity.Apple">
        <property name="title" value="青苹果"/>
        <property name="color" value="绿色"/>
        <property name="origin" value="中亚"/>
    </bean>

    <bean id="lily" class="com.imooc.Apple.entity.Child">
        <property name="name" value="莉莉"/>
        <property name="apple" ref="sweetApple"/>
    </bean>
</beans>
package com.imooc.Apple;

import com.imooc.Apple.entity.Apple;
import com.imooc.Apple.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
        Apple sweetApple = context.getBean("sweetApple", Apple.class);
        Child lily = context.getBean("lily", Child.class);
        // 添加打印语句,检查属性是否正确注入
        System.out.println("lily's name: " + lily.getName());
        System.out.println("lily's apple origin: " + lily.getApple().getOrigin());
        System.out.println("lily's apple title: " + lily.getApple().getTitle());
        System.out.println("====================================");
//        System.out.println(sweetApple.getTitle()+sweetApple.getColor()+sweetApple.getOrigin());
        lily.eat();
        sweetApple.apple();
    }
}

注入集合对象_1

注入List [允许重复数据]
<bean id = "..." class = "...">
    <property name = "someList">
    <list>
        <value>具体值</value>
        <ref bean="beanId"></ref>
    </list>
    </property>
</bean>
注入Set [不允许重复数据 自动去除重复]
<bean id = "..." class = "...">
    <property name = "someSet">
    <set>
        <value>具体值</value>
      <ref bean="beanId"></ref>
    </set>
    </property>
</bean>
注入Map
<bean id = "..." class = "...">
    <property name = "someMap">
    <Map>
        <entry key="k1" value="v1"></entry> #静态数值
      <entry key="k2" value-ref="beanId"></entry> #对象引用
    </Map>
    </property>
</bean>
注入Properties
<bean id = "..." class = "...">
    <property name = "someProperties">
    <props>
        <prop key="k1">v1</prop>
      <prop key="k2">v2</prop>
    </props>
    </property>
</bean>

公司资产配置清单[小案例]

constructor-arg:通过 构造函数注入 。

property:通过 setter对应的方法注入

Company.java
package com.imooc.spring.ioc.entity;

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class Company {
    private List<String> rooms;
    private Map<String,Computer> computers; //每一条数据保存的都是Computer型
    private Properties info;//键值对的写

    @Override
    public String toString() {
        return "Company{" +
                "rooms=" + rooms +
                ", computers=" + computers +
                ", info=" + info +
                '}';
    }
    + Getter Setter
}
Computer.java
public class Computer {
    private String brand;
    private String type;
    private String sn;
    private Float price;
    constructor(空+满)+Getter Setter
}

如何在ioc容器创建后自动的实例化Company对象并且填充信息呢?
所有的工作都在applicationContext.xml中进行的

applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="company" class="com.imooc.spring.ioc.entity.Company">
        <property name="rooms">
            <list> //【允许重复】
                <value>2001-总裁办</value>
                <value>2003-总经理办公室</value>
                <value>2010-研发部会议室</value>
            </list>
        </property>
    </bean>
</beans>
SpringApplication.java
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Company;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Company company = context.getBean("company", Company.class);
        System.out.println(company);
    }
}
------------------------------------------------------------
Company{rooms=[2001-总裁办, 2003-总经理办公室, 2010-研发部会议室], computers=null, info=null}

Process finished with exit code 0
------------------------------------------------------------
下方xml更新后的显示数据:
Company{rooms=[2001-总裁办, 2003-总经理办公室, 2010-研发部会议室], computers={dev-88172=Computer{brand='联想', type='台式机', sn='8389283012', price=3085.0}, dev-88173=Computer{brand='联想', type='台式机', sn='8389283012', price=3085.0}}, info={phone=010-12345678, address=北京市朝阳区XX路XX大厦, website=https://p-luminary.github.io}}
https://p-luminary.github.io

Process finished with exit code 0

list的底层是ArrayList
Set的底层是LinkedHashSet [双向有序列表]
Map的底层是LinkedHashMap [双向列表 提取也是存放顺序]

applicationContext.xml [更新一次]
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="c1" class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="联想"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="8389283012"/>
        <constructor-arg name="price" value="3085"/>
    </bean>
    <bean id="company" class="com.imooc.spring.ioc.entity.Company">
        <property name="rooms">
            <set>
                <value>2001-总裁办</value>
                <value>2003-总经理办公室</value>
                <value>2010-研发部会议室</value>
                <value>2010-研发部会议室</value>
            </set>
        </property>
        <property name="computers">
            <map>
                <entry key="dev-88172" value-ref="c1"/> <!-- 对象引用c1在上方 -->
            </map>
        </property>
    </bean>
</beans>

或者【简便方法】↓
applicationContext.xml 更新二次【新增简便方法】
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="c1" class="com.imooc.spring.ioc.entity.Computer">【方法一】
        <constructor-arg name="brand" value="联想"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="8389283012"/>
        <constructor-arg name="price" value="3085"/>
    </bean>
    <bean id="company" class="com.imooc.spring.ioc.entity.Company">
        <property name="rooms">
            <set>
                <value>2001-总裁办</value>
                <value>2003-总经理办公室</value>
                <value>2010-研发部会议室</value>
                <value>2010-研发部会议室</value>
            </set>
        </property>
        
        <property name="computers">
            <map>
                <entry key="dev-88172" value-ref="c1"/> <!-- 对象引用c1在上方 -->
                <entry key="dev-88173">【方法二 (推荐)】
                    <bean class="com.imooc.spring.ioc.entity.Computer">
                        <constructor-arg name="brand" value="联想"/>
                        <constructor-arg name="type" value="台式机"/>
                        <constructor-arg name="sn" value="8389283012"/>
                        <constructor-arg name="price" value="3085"/>
                    </bean>
                </entry>
            </map>
        </property>
        
        <property name="info">
            <props>
                <prop key="phone">010-12345678</prop>
                <prop key="address">北京市朝阳区XX路XX大厦</prop>
                <prop key="website">https://p-luminary.github.io</prop>
            </props>
        </property>
    </bean>
</beans>

查看容器内对象 【.getBeanDefinitionNames()】

多个同类型的bean会自动增加编号 #1  #2
    <bean class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="微星"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="8389283012"/>
        <constructor-arg name="price" value="3000"/>
    </bean>
    <bean class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="华硕"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="9023283012"/>
        <constructor-arg name="price" value="5600"/>
    </bean>

//获取容器内所有beanId数组       
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName:beanNames){
            System.out.println(beanName); //c1 company ...<bean id="...">
             System.out.println("类型:" + context.getBean(beanName).getClass().getName());
            System.out.println("内容:" + context.getBean(beanName).toString());
        }
        Computer computer = context.getBean("com.imooc.spring.ioc.entity.Computer", Computer.class);
        System.out.println(computer.getBrand());
        Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#1", Computer.class);
        System.out.println(computer1.getBrand());

bean scope属性

  • bean scope属性用于决定对象何时被创建与作用范围
  • bean scope配置将影响容器内对象的数量
  • bean scope默认值singleton(单例), 指全局共享同一个对象实例
scope用法
<bean id="bookDao"
    class="com.imooc.spring.ioc.bookshop.dao.BookDaoOracleImpl"
    scope="prototype"/>
bean scope属性清单
scope属性 说明
singleton 单例(默认值),每一个容器有且只有唯一的实例,实例被全局共享
prototype 多例,每次使用时都是创建一个实例
request web环境下,每一个独立请求存在唯一实例
session web环境下,每一个session存在有唯一实例
application web环境下,ServletContext存在唯一实例
websocket 每一次WebSocket连接中存在唯一实例

singleton与prototype对比
singleton prototype
对象数量 全局唯一 存在多个
实例化时机 IoC容器启动时 getBean() 或 对象注入时
线程安全问题 存在 不存在
执行效率
UserDao.java
package com.imooc.spring.ioc.dao;

public class UserDao {
    public UserDao(){
        System.out.println("UserDao已创建: " + this);
    }
}
UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.UserDao;

public class UserService {
    public UserDao userDao;

    public UserService() {
        System.out.println("UserService已创建: " + this);
    }

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        System.out.println("调用setUserDao: " + userDao);
        this.userDao = userDao;
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>  <!-- 多例 -->
   <bean id="userService" class="com.imooc.spring.ioc.service.UserService" scope="prototype"> <!-- 单例 创建一个对象 -->
      <property name="userDao" ref="userDao"/> <!-- 引用了上面的userDao多例 再创建一个对象 -->
   </bean>

</beans>
SpringApplication.java 【单例】

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserDao userDao = context.getBean("userDao", UserDao.class);
        UserDao userDao1 = context.getBean("userDao", UserDao.class);
        UserDao userDao2 = context.getBean("userDao", UserDao.class);
        UserDao userDao3 = context.getBean("userDao", UserDao.class);
    }
}

applicationContext.xml
<bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>  <!-- 多例 -->
   <bean id="userService" class="com.imooc.spring.ioc.service.UserService" > <!-- 单例 创建一个对象 -->
      <property name="userDao" ref="userDao"/> <!-- 引用了上面的userDao多例 再创建一个对象 -->
   </bean>

================================================================
输出:
UserService已创建: com.imooc.spring.ioc.service.UserService@3dd3bcd
UserDao已创建: com.imooc.spring.ioc.dao.UserDao@7c16905e
调用setUserDao: com.imooc.spring.ioc.dao.UserDao@7c16905e
    
Process finished with exit code 0
SpringApplication.java 【多例】
public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        System.out.println("IoC容器已初始化");
        UserService userService1 = context.getBean("userService", UserService.class);
        UserService userService2 = context.getBean("userService", UserService.class);
        UserService userService3 = context.getBean("userService", UserService.class);
        UserService userService4 = context.getBean("userService", UserService.class);
    }
}

applicationContext.xml
<bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>  <!-- 多例 -->
   <bean id="userService" class="com.imooc.spring.ioc.service.UserService" scope="prototype"> <!-- 单例 创建一个对象 -->
      <property name="userDao" ref="userDao"/> <!-- 引用了上面的userDao多例 再创建一个对象 -->
   </bean>

================================================================
输出:
IoC容器已初始化
UserService已创建: com.imooc.spring.ioc.service.UserService@5c7fa833
UserDao已创建: com.imooc.spring.ioc.dao.UserDao@4dfa3a9d
调用setUserDao: com.imooc.spring.ioc.dao.UserDao@4dfa3a9d
UserService已创建: com.imooc.spring.ioc.service.UserService@4b952a2d
UserDao已创建: com.imooc.spring.ioc.dao.UserDao@3159c4b8
调用setUserDao: com.imooc.spring.ioc.dao.UserDao@3159c4b8
UserService已创建: com.imooc.spring.ioc.service.UserService@73846619
UserDao已创建: com.imooc.spring.ioc.dao.UserDao@4bec1f0c
调用setUserDao: com.imooc.spring.ioc.dao.UserDao@4bec1f0c
UserService已创建: com.imooc.spring.ioc.service.UserService@29ca901e
UserDao已创建: com.imooc.spring.ioc.dao.UserDao@5649fd9b
调用setUserDao: com.imooc.spring.ioc.dao.UserDao@5649fd9b

Process finished with exit code 0

一般来说dao类 service类 control类都是单例 因为单例安全根源是运行时发生不断的变化 如果在真正环境中一般不会重新设置那些类,在运行中都是恒定不变的。所以推荐用单例默认singleton

对象生命周期

Order.java
package com.imooc.spring.ioc.entity;

public class Order {
    private Float price;
    private Integer quantity;
    private Float total;

    public Order() {
        System.out.println("创建Order对象" + this);
    }

    public void init(){
        System.out.println("执行init()方法");
        total = price * quantity;
    }
    public void pay(){
        System.out.println("订单金额为:" + total);
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        System.out.println("设置price:" + price);
        this.price = price;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        System.out.println("设置quantity:" + quantity);
        this.quantity = quantity;
    }

    public Float getTotal() {
        return total;
    }

    public void setTotal(Float total) {
        this.total = total;
    }

    public void destroy(){
        System.out.println("释放与订单对象相关的资源");
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>  <!-- 多例 -->
    <bean id="userService" class="com.imooc.spring.ioc.service.UserService" scope="prototype"> <!-- 单例 创建一个对象 -->
        <property name="userDao" ref="userDao"/> <!-- 引用了上面的userDao多例 再创建一个对象 -->
    </bean>
    <bean id="order1" class="com.imooc.spring.ioc.entity.Order" init-method="init" destroy-method="destroy">
        <property name="price" value="19.8"/>
        <property name="quantity" value="1000"/>
    </bean>
</beans>
SpringApplication.java
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Order;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        System.out.println("======IoC容器已初始化======");
        Order order1 = context.getBean("order1",Order.class);
        order1.pay();
        ((ClassPathXmlApplicationContext)context).registerShutdownHook(); //销毁容器 自动调用bean中设置的destory方法
    }
}

==========================================
创建Order对象com.imooc.spring.ioc.entity.Order@153f5a29
设置price:19.8
设置quantity:1000
执行init()方法
======IoC容器已初始化======
订单金额为:19800.0
释放与订单对象相关的资源

Process finished with exit code 0

实现极简IoC容器 [利用反射机制完成对象的创建和注入]

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Spring_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

<!--        Dom4j是Java的XML解析组件-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
<!--        jaxen是Xpath表达式解释器-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>
Apple.java
public class Apple {
    private String title;
    private String color;
    private String origin;
} + Getter Setter
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id = "sweetApple" class="com.imooc.spring.ioc.context.Apple">
        <property name="title" value="红富士"/> //这里的title是在setter中 public void setTitle(String title){this.title=title;}
        <property name="color" value="红色"/>
        <property name="origin" value="欧洲"/>
    </bean>
</beans>
ApplicationContext.java[接口]
package com.imooc.spring.ioc.context;

public interface ApplicationContext {
    public Object getBean(String beanId);
}
ClassPathXmlApplicationContext.java
package com.imooc.spring.ioc.context;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ClassPathXmlApplicationContext implements ApplicationContext{
    private Map iocContainer = new HashMap();
    public ClassPathXmlApplicationContext(){
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
            filePath = new URLDecoder().decode(filePath,"UTF-8");
            SAXReader reader = new SAXReader();
            Document document = reader.read(new File(filePath));
            List<Node> beans = document.getRootElement().selectNodes("bean");//得到节点的集合
            for (Node node:beans){
                Element ele = (Element) node;
                String id = ele.attributeValue("id");
                String className = ele.attributeValue("class");
                Class c = Class.forName(className);//加载指定类
                Object obj = c.newInstance();
                List<Node> properties = ele.selectNodes("property");
                for (Node p:properties){
                    Element property = (Element) p;
                    String propName = property.attributeValue("name");
                    String propValue= property.attributeValue("value");
//基于property完成注入是通过Setter的set方法,set方法命名的格式为setTitle 属性名第一个字母有个大写
                    String setMethodName = "set" + propName.substring(0,1).toUpperCase()+propName.substring(1);
                    System.out.println("准备执行" + setMethodName + "方法注入数据");
                    Method setMethod = c.getMethod(setMethodName, String.class);
                    setMethod.invoke(obj, propValue); //执行哪个对象的实例方法
                }
                iocContainer.put(id,obj); //赋予了bean id
            }
            System.out.println(iocContainer);
            System.out.println("IoC容器初始化完毕");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public Object getBean(String beanId) {
        return iocContainer.get(beanId);
    }
}
Application.java
package com.imooc.spring.ioc.context;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext();
        Apple apple = (Apple) context.getBean("sweetApple");
        System.out.println(apple);
    }
}

=================================================
准备执行setTitle方法注入数据
准备执行setColor方法注入数据
准备执行setOrigin方法注入数据
{sweetApple=com.imooc.spring.ioc.context.Apple@484b61fc}
IoC容器初始化完毕
com.imooc.spring.ioc.context.Apple@484b61fc

Process finished with exit code 0

四种组件类型注解

基于注解配置IoC容器
基于注解的优势
  • 摆脱繁琐的XML形式的bean与依赖注入配置
  • 基于”声明式”的原则,更适合轻量级的现代企业应用
  • 让代码可读性变得更好,研发人员拥有更好的开发体验
三类注解
  • 四种组件类型注解-声明当前类的功能与职责
注解 说明
@Component 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化
@Controller 语义注解,说明当前类是MVC应用中的控制器类
@Service 语义注解,说明当前类是Service业务服务类
@Repository 语义注解,说明当前类用于业务持久层,通常描述对应Dao类

开启组件扫描

XML配置开启组件扫描,才能使用注解
<context:component-scan base-package="com.imooc">
   <context:exclude-filter type="regex" expression="com.imooc.exl.*"/>
</context:component-scan>
  • 自动装配注解-根据属性特征自动注入对象
  • 元数据注解-更细化的辅助IoC容器管理对象的注解

基于注解初始化IoC容器

Core Technologies (spring.io)

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
<!--
    在IoC容器初始化时自动扫描四种组件类型注解并完成实例化
    @Repository
    @Service
    @Controller
    @Component
-->
    <context:component-scan base-package="com.imooc"/> //设置基准的包名去扫描

</beans>
controller/UserController.java
package com.imooc.spring.ioc.controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
}
dao/UserDao.java
package com.imooc.spring.ioc.dao;

import org.springframework.stereotype.Repository;

//组件类型解释默认beanId为类名首字母小写
//组件类型解释默认beanId为类名首字母小写
//beanId = userDao
@Repository
public class UserDao {

}
service/UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.UserDao;
import org.springframework.stereotype.Service;

@Service
public class UserService {

}
utils/StringUtils.java
package com.imooc.spring.ioc.utils;

import org.springframework.stereotype.Component;

@Component
public class StringUtils {
}
SpringApplication.java
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Order;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        String[] ids = context.getBeanDefinitionNames();
        for (String id:ids){ //这些bean在容器中是单例,在初始化的时候创建对象
            System.out.println(id+":"+context.getBean(id));
        }
    }
}
==============================================================
//组件类型解释默认beanId为类名首字母小写
userController:com.imooc.spring.ioc.controller.UserController@4f51b3e0
userDao:com.imooc.spring.ioc.dao.UserDao@4b9e255
userService:com.imooc.spring.ioc.service.UserService@5e57643e
stringUtils:com.imooc.spring.ioc.utils.StringUtils@133e16fd
    
org.springframework.context.annotation.internalConfigurationAnnotationProcessor:org.springframework.context.annotation.ConfigurationClassPostProcessor@51b279c9
org.springframework.context.annotation.internalAutowiredAnnotationProcessor:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@1ad282e0
org.springframework.context.annotation.internalCommonAnnotationProcessor:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@7f416310
org.springframework.context.event.internalEventListenerProcessor:org.springframework.context.event.EventListenerMethodProcessor@1cab0bfb
org.springframework.context.event.internalEventListenerFactory:org.springframework.context.event.DefaultEventListenerFactory@5e955596

Process finished with exit code 0

自动装配与Autowired注解

两类自动装配注解 [@Primary 如果有两个写此注解作为主要]
分类 注解 说明
按类型装配 @Autowired 按容器内对象类型动态注入属性,由Spring机构提供
@Inject 基于JSR-330标准,其他同@Autowired,但不支持required属性
按名称装配 @Named 与@Inject配合使用,JSR-330规范,按属性名自动装配属性
@Resource 基于JSR-250规范,优先按名称,再按类型智能匹配
模式 说明
no 这是默认的自动装配模式,意味着默认情况下没有自动装配
byName byName模式根据bean的名称注入对象依赖项。在这种情况属性名称和bean名称必须相同,它在内部调用setter方法
byType byType模式根据类型注入对象依赖项,因此属性名称和bean名称可以不同,它在内部调用setter方法
constructor 构造函数模式通过调用类的构造函数来注入依赖项。它会调用具有大量参数的构造函数。
autodetect 从Spring 3开始不推荐使用
无法确认注入哪个bean 解决方案【UserDao 与 UserOracleDao】
UserDao.java
package com.imooc.spring.ioc.dao;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

//组件类型解释默认beanId为类名首字母小写
//beanId = userDao
@Repository
public class UserDao implements IUserDao{
    public UserDao(){
        System.out.println("正在创建UseDao:" + this);
    }
}
UserOracleDao.java
package com.imooc.spring.ioc.dao;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
@Repository
@Primary
public class UserOracleDao implements IUserDao{

    public UserOracleDao(){
        System.out.println("正在创建UserOracleDao:" + this);
    }
}
IUserDao.java [接口]
package com.imooc.spring.ioc.dao;

public interface IUserDao {
}
DepartmentService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.IUserDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class DepartmentService {
    /**
     * 1. @Resource设置name属性,则按name在IoC容器中将bean注入
     * 2. @Resource未设置name属性
     *  2.1 以属性名作为bean name在IoC容器中匹配bean,如有匹配则注入
     *  2.2 按属性名未匹配,则按类型进行匹配,同 @Autowired 需要加入 @Primary解决类型冲突
     *  使用建议: 在使用 @Resource对象时推荐设置name或保证属性名与bean名称一致
     */
    /*方法2.2.1
    @Resource(name = "userOracleDao")
    private IUserDao udao;*/

    //方法2.2.2
    @Resource
    private IUserDao userOracleDao;
    public void joinDepartment(){
        System.out.println(userOracleDao);
    }
}
UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.IUserDao;
import com.imooc.spring.ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    //@Autowired
    //Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public,直接进行赋值
    //不再执行set方法
    private IUserDao udao;

    public UserService() {
        System.out.println("正在构建UserService" + this);
    }

    public IUserDao getUdao() {
        return udao;
    }

    @Autowired
    //如果装配注解放在set方法上,则自动按类型/名称对set方法参数进行注入
    public void setUdao(UserDao udao) {
        System.out.println("setUdao: " + udao);
        this.udao = udao;
    }
}
SpringApplication.java 
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Order;
import com.imooc.spring.ioc.service.DepartmentService;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService.getUdao());
        DepartmentService departmentService = context.getBean("departmentService", DepartmentService.class);
        departmentService.joinDepartment();
    }
}

元数据注解

注解 说明
@Primary 按类型装配时出现多个相同类型对象,拥有此注解对象优先被注入
@PostConstruct 描述方法,相当于XML中init-method配置的注解版本
@PreDestory 描述方法,相当于XML中destory-method配置的注解版本
@Scope 设置对象的scope属性 //@Scope(“prototype”)设置多例
@Value 为属性注入静态数据
applicationContext.xml 【新增第11行】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
<!--    加载指定路径下的properties文件-->
    <context:property-placeholder location="classpath:config.properties"/>
<!--
    在IoC容器初始化时自动扫描四种组件类型注解并完成实例化
    @Repository
    @Service
    @Controller
    @Component
-->
    <context:component-scan base-package="com.imooc"/>

</beans>
config.properties [这里属于动态注入噢~]
  metaData=imooc.com
UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.IUserDao;
import com.imooc.spring.ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
@("prototype") //设置多例 与XML中bean scope完全相同
public class UserService {
    @Value("${metaData}")
    private String metaData; //对此数据的动态注入

    //@Autowired
    //Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public,直接进行赋值
    //不再执行set方法
    public UserService() {
        System.out.println("正在构建UserService" + this);
    }

    @PostConstruct //XML中bean init-method完全相同
    public void init(){
        System.out.println("初始化UserService对象,metaData=" + metaData);
    }
    private IUserDao udao;


    public IUserDao getUdao() {
        return udao;
    }

    @Autowired
    //如果装配注解放在set方法上,则自动按类型/名称对set方法参数进行注入
    public void setUdao(UserDao udao) {
        System.out.println("setUdao: " + udao);
        this.udao = udao;
    }
}
SpringApplication.java
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Order;
import com.imooc.spring.ioc.service.DepartmentService;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService.getUdao());
    }
}

基于Java Config配置IoC容器

  • 完全摆脱XML的束缚, 使用独立Java类管理对象与依赖
  • 注解配置相对分散, 利用Java Config可对配置集中管理
  • 可以在编译时进行依赖检查, 不容易出错
Java Config核心注解【敏捷开发 小型项目】 而XML是大型团队项目
注解 说明
@Configuration 描述类, 说明当前类是Java Config配置类, 完全替代XML文件
@Bean 描述方法, 方法返回对象将被IoC容器管理, beanId默认为方法名
@ImportResource 描述类, 加载静态文件, 可使用@Value注解获取
@ComponentScan 描述类, 同XML的 < context:compoment-scan >标签
UserController.java
package com.imooc.spring.ioc.controller;

import com.imooc.spring.ioc.service.UserService;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    private UserService userService;

    public UserService getUserService() {
        return userService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}
UserDao.java
package com.imooc.spring.ioc.dao;

public class UserDao {
}
UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.UserDao;

public class UserService {
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
Config.java [作为配置文件]
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.controller.UserController;
import com.imooc.spring.ioc.dao.UserDao;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration //当前是个配置类,用于代替applicationContext.xml
public class Config {
    @Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
    public UserDao userDao(){
        UserDao userDao = new UserDao();
        return userDao;
    }
    @Bean
    public UserService userService(){
        UserService userService = new UserService();
        return userService;
    }
    @Bean
    public UserController userController(){
        UserController userController = new UserController();
        return userController;
    }
}
SpringApplication.java
package com.imooc.spring.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        //基于Java Config配置IoC容器的初始化
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        String[] ids = context.getBeanDefinitionNames();
        for (String id:ids){
            System.out.println(id+":"+context.getBean(id));
        }
    }
}
=======================================================
org.springframework.context.annotation.internalConfigurationAnnotationProcessor:org.springframework.context.annotation.ConfigurationClassPostProcessor@4f9a3314
org.springframework.context.annotation.internalAutowiredAnnotationProcessor:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@3b2c72c2
org.springframework.context.annotation.internalCommonAnnotationProcessor:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@491666ad
org.springframework.context.event.internalEventListenerProcessor:org.springframework.context.event.EventListenerMethodProcessor@176d53b2
org.springframework.context.event.internalEventListenerFactory:org.springframework.context.event.DefaultEventListenerFactory@971d0d8
config:com.imooc.Config.Config$$EnhancerBySpringCGLIB$$c932e406@51931956
userDao:com.imooc.Config.UserDao@2b4a2ec7
userService:com.imooc.Config.UserService@564718df
userController:com.imooc.Config.UserController@51b7e5df

JavaConfig-对象依赖注入

Config的括号增加参数

Config.java
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.controller.UserController;
import com.imooc.spring.ioc.dao.UserDao;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration //当前是个配置类,用于代替applicationContext.xml
@ComponentScan(basePackages = "com.imooc") //扫描其他的组件类 因为其他人也会写
public class Config {
    @Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
    public UserDao userDao(){
        UserDao userDao = new UserDao();
        System.out.println("已创建"+userDao);
        return userDao;
    }
    @Bean
    //先按name尝试注入,name不存在则按类型注入 冲突则@Primary
    public UserService userService(UserDao userDao){
        UserService userService = new UserService();
        System.out.println("已创建"+userService);
        userService.setUserDao(userDao);
        System.out.println("调用setUserDao:" + userDao);
        return userService;
    }
    @Bean //<bean id="xxx" class="xxx">
    public UserController userController(UserService userService){
        UserController userController = new UserController();
        System.out.println("已创建" + userController);
        userController.setUserService(userService);
        System.out.println("调用setUserService:"+userService);
        return userController;
    }
}
SpringApplication.java
package com.imooc.spring.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        //基于Java Config配置IoC容器的初始化
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        System.out.println("=============================");
        String[] ids = context.getBeanDefinitionNames();
        for (String id:ids){
            System.out.println(id+":"+context.getBean(id));
        }
    }
}

=================================================================
已创建com.imooc.spring.ioc.dao.UserDao@10b48321
已创建com.imooc.spring.ioc.service.UserService@473b46c3
调用setUserDao:com.imooc.spring.ioc.dao.UserDao@10b48321
已创建com.imooc.spring.ioc.controller.UserController@797badd3
调用setUserService:com.imooc.spring.ioc.service.UserService@473b46c3
=============================
org.springframework.context.annotation.internalConfigurationAnnotationProcessor:org.springframework.context.annotation.ConfigurationClassPostProcessor@44a664f2
org.springframework.context.annotation.internalAutowiredAnnotationProcessor:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@7f9fcf7f
org.springframework.context.annotation.internalCommonAnnotationProcessor:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@2357d90a
org.springframework.context.event.internalEventListenerProcessor:org.springframework.context.event.EventListenerMethodProcessor@6328d34a
org.springframework.context.event.internalEventListenerFactory:org.springframework.context.event.DefaultEventListenerFactory@145eaa29
config:com.imooc.spring.ioc.Config$$EnhancerBySpringCGLIB$$8a99aa4c@15bb6bea
userDao:com.imooc.spring.ioc.dao.UserDao@10b48321
userService:com.imooc.spring.ioc.service.UserService@473b46c3
userController:com.imooc.spring.ioc.controller.UserController@797badd3

Process finished with exit code 0
如果突然想增加一个注入employeeDao
UserService.java 先增加一个私有的名字 再getter+setter
去Conifg.java 括号里新增
public UserService userService(UserDao userDao, EmployeeDao employeeDao){
        UserService userService = new UserService();
        System.out.println("已创建"+userService);
        userService.setUserDao(userDao);
        System.out.println("调用setUserDao:" + userDao);
        userService.setEmployeeDao(employeeDao);
        return userService;
    }

Spring Test测试模块

  • Spring Test是Spring中用于测试的模块
  • Spring Test对JUnit单元测试框架有良好的整合
  • 通过Spring Test可在Junit单元测试时自动初始化IoC容器
Spring与JUnit4整合过程
  • Maven工程依赖spring-test
  • 利用 @RunWith@ContextConfiguration描述测试用例类
  • 测试用例类从容器获取对象完成测试用例的执行
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Spring_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
        <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>
UserDao.java
package com.imooc.spring.ioc.dao;

public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}
UserService.java
package com.imooc.spring.ioc.service;

import com.imooc.spring.ioc.dao.UserDao;

public class UserService {
    private UserDao userDao;
    public void createUser(){
        System.out.println("调用创建用户业务代码");
        userDao.insert();
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao">

    </bean>

    <bean id="userService" class="com.imooc.spring.ioc.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
SpringTestor.java
import com.imooc.spring.ioc.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

//将Junit4的执行权交给Spring Test,在测试用例执行前自动初始化IoC容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringTestor {
    @Resource
    private UserService userService;

    @Test
    public void testUserService(){
        userService.createUser();
    }
}


介绍AOP

Aspect Oriented Programming(AOP)从某种意义上说是对OOP的补充,因为它还提供了模块化的功能。但是模块化的关键单元是方面而不是类。

AOP面向切面编程
  • 介绍Spring AOP与相关概念名词
  • Spring AOP开发与配置流程
  • Spring 五种通知类型与应用场景
Spring中的可插拔组件技术 [类似于插件]

应用程序执行前检测的作用

Spring AOP [在不修改源码的情况下对程序进行扩展]

[IDEA创建项目出现Cannot resolve plugin org.apache.maven.plugins:maven-clean-plugin:2.5-CSDN博客](https://blog.csdn.net/weixin_45429409/article/details/118068484?ops_request_misc=%7B%22request%5Fid%22%3A%22170678007016800227455895%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170678007016800227455895&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-118068484-null-null.142^v99^pc_search_result_base9&utm_term=Cannot resolve plugin org.apache.maven.plugins%3Amaven-site-plugin%3A3.3&spm=1018.2226.3001.4187)

  • Spring AOP - Aspect Oriented Programming 面向切面编程
  • AOP的做法是将通用、与业务无关的功能抽象封装为切面类
  • 切面可配置在目标方法的执行前、后运行,真正做到即插即用

运行前进行拦截在运行前打印时间 再运行代码;没有IoC就没有AOP;写完扩展之后要再applicationContext.xml中新增< aop:config >…来认可切面

aop/dao/EmployeeDao.java
package com.imooc.spring.aop.dao;

/**
 * 员工表Dao
 */
public class EmployeeDao {
    public void insert(){
        System.out.println("新增员工数据");
    }
}
---------------------------------------------
aop/dao/UserDao.java
package com.imooc.spring.aop.dao;

/**
 * 用户表Dao
 */
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}
---------------------------------------------
aop/service/EmployeeService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.EmployeeDao;

import java.util.Date;

/**
 * 员工服务
 */
public class EmployeeService {
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("执行员工入职业务逻辑");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}
---------------------------------------------
aop/service/UserService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.UserDao;

/**
 * 用户服务
 */
public class UserService {
    private UserDao userDao;

    public void createUser(){
        if(1==1){
            throw new RuntimeException("用户已存在");
        }
        System.out.println("执行创建用户业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
---------------------------------------------
aop/aspect/MethodAspect
package com.imooc.spring.aop.aspect;

import org.aspectj.lang.JoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

//切面类
public class MethodAspect {
    //切面方法,用于扩展额外功能
    //JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
    public void printExecutionTime(JoinPoint joinPoint){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now = sdf.format(new Date());
        String className = joinPoint.getTarget().getClass().getName();//获取类→获取目标类的名称
        String methodName = joinPoint.getSignature().getName();//获取目标方法名称
        System.out.println("---->" + now + ":" + className + "." + methodName);
        Object[] args = joinPoint.getArgs();
        System.out.println("---->参数个数:" + args.length);
        for(Object arg:args){
            System.out.println("---->参数:" + arg);
        }
    }

    public void doAfterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("<----返回后通知:" + ret);
    }
    public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
        System.out.println("<----异常通知:" + th.getMessage());
    }
    public void doAfter(JoinPoint joinPoint){
        System.out.println("<----触发后置通知");
    }
}
SpringApplication.java
package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser(); //模拟创建新用户过程
        userService.generateRandomPassword("MD5", 16);
    }
}
====================================================================
---->2024-02-01 18:09:38 050:com.imooc.AOP.UserService.createUser
---->参数个数:0
执行创建用户业务逻辑
新增用户数据
<----返回后通知:null
<----触发后置通知
---->2024-02-01 18:09:38 058:com.imooc.AOP.UserService.generateRandomPassword
---->参数个数:2
---->参数:MD5
---->参数:16
按MD5方式生成16位随机密码
<----返回后通知:PzZo3Fzqe!r4$
<----触发后置通知
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.imooc.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>
    <!-- AOP配置 迎接新MethodAspect切面的到来-->
    <bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"></bean>
    <aop:config>
        <!-- PointCut 切点,使用execution表达式描述切面的作用范围 -->
        <!-- execution(public * com.imooc..*.*(..)) 说明切面作用在com.imooc包下的所有类的所有方法上 -->
        <!--<aop:pointcut id="pointcut" expression="execution(public * com.imooc..*.*(..))"></aop:pointcut>-->
        <!--只对所有Service类生效-->
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"></aop:pointcut>
        <!--只对所有返回值为String类型方法生效-->
        <!--<aop:pointcut id="pointcut" expression="execution(String com.imooc..*Service.*(..))"></aop:pointcut>-->
        <!--对方法名进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.create*(..))"></aop:pointcut>-->
        <!-- 对参数进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(String,*))"></aop:pointcut>-->
        <!-- 定义切面类 -->
        <aop:aspect ref="methodAspect"> <!--关联一下-->
            <!-- before通知(Advice),代表在目标方法运行前先执行methodAspect.printExecutionTime() -->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
            <aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
            <aop:after method="doAfter" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc.spring</groupId>
    <artifactId>aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--aspectjweaver是Spring AOP的底层依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>
</project>

Spring AOP 与 AspectJ的关系 [实现类与方法的匹配]

Spring【SpringAOP(通知类型、切点表达式 、多切面配置 、注解配置AOP、原生Spring实现AOP)】(六)-全面详解(学习总结—从入门到深化)_童小纯呀的技术博客_51CTO博客

  • Eclips AspectJ 是一种基于Java平台的面向切面编程的语言
  • Spring AOP使用AspectJWeaver实现类与方法匹配
  • Spring AOP利用代理模式实现对象运行时功能扩展
几个关键概念
注解 说明
Aspect 切面,具体的可插拔组件功能类,通常一个切面只能实现一个通用功能
Target Class/Method 目标类、目标方法,指真正要执行与业务相关的方法
PointCut 切入点,使用execution表达式说明切面要作用再系统的哪些类上
JoinPoint 连接点,切面运行过程中是包含了目标类/方法元数据的对象
Advice 通知,说明具体的切面的执行时机,Spring包含了不中不同类型通知

JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
public void printExecutionTime(JoinPoint joinPoint){}

Spring AspectJ AOP实现提供了许多注释:

@Aspect 将该类声明为方面。
@Pointcut 声明切入点表达式。

用于创建建议的注释如下:

@Before 声明before建议。在调用实际方法之前将其应用。
@After 声明after建议。在调用实际方法之后并返回结果之前应用。
@AfterReturning 声明返回建议之后。在调用实际方法之后并返回结果之前应用。但是您可以在建议中获得结果值。
@Around 声明环绕建议。它在调用实际方法之前和之后应用。
@AfterThrowing 声明了throws建议。如果实际方法引发异常,则应用此方法。

AOP配置过程
  • 依赖AspectJ
  • 实现切面类/方法
  • 配置Aspect Bean
  • 定义PointCut
  • 配置Advice //before通知(Advice)

JoinPoint核心方法

注解 说明
Object getTarget() 获取IoC容器内目标对象
Signature getSignature() 获取目标方法
Object[] getArgs() 获取目标方法参数
//很多线上的项目需要跟踪调试却不知道输入的参数是什么 就可以增加切片
public void printExecutionTime(JoinPoint joinPoint){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now = sdf.format(new Date());
        String className = joinPoint.getTarget().getClass().getName();//获取类→获取目标类的名称
        String methodName = joinPoint.getSignature().getName();//获取目标方法名称
        System.out.println("---->" + now + ":" + className + "." + methodName);
        Object[] args = joinPoint.getArgs(); //传入目标方法的实际参数
        System.out.println("---->参数个数:" + args.length);
        for(Object arg:args){
            System.out.println("---->参数:" + arg);
        }
    }

PointCut切点表达式 [@pointcut]

注释用于定义切入点。我们也可以通过名称引用切入点表达式

@Pointcut("execution(* Operation.*(..))")
private void doSomething() {}

//它将应用于所有公共方法
@Pointcut("execution(public * * (..))")

//它将应用于Operation类的所有公共方法
@Pointcut("execution(public Operation.*(..))")

//它将应用于Operation类的所有方法
@Pointcut("execution(* Operation.*(..))")

//它将应用于Employee类的所有公共设置方法
@Pointcut("execution(public Employee.set*(..))")

//它将应用于所有返回int值的Operation类方法
@Pointcut("execution(int Operation.*(..))")

​ public void com.imooc.service.UserService.createUser(形参1,形参2,..)
​ ↑ ↑ ↑ ↑ ↑ ↑ ↑

execution(public * com.imooc .. * . * ( .. ))
*通配符 ..包通配符[包括所有后代查找] (..)参数通配符

第一项public可以默认

<!-- AOP配置 迎接新MethodAspect切面的到来-->
    <bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"></bean>
    <aop:config>
        <!-- PointCut 切点,使用execution表达式描述切面的作用范围 -->
        <!-- execution(public * com.imooc..*.*(..)) 说明切面作用在com.imooc包下的所有类的所有方法上 -->
        <!--<aop:pointcut id="pointcut" expression="execution(public * com.imooc..*.*(..))"></aop:pointcut>-->
        <!--只对所有Service类生效-->
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"></aop:pointcut>
    </aop:config>

---------------------------------------------------------------------------------
切点表达式训练
<!--只对所有返回值为String类型方法生效-->
        <!--<aop:pointcut id="pointcut" expression="execution(String com.imooc..*Service.*(..))"></aop:pointcut>-->
        <!--对方法名进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.create*(..))"></aop:pointcut>-->
        <!-- 对参数进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(String,*))"></aop:pointcut>-->
        <!-- 定义切面类 -->

五种通知类型

注解 说明
Before Advice 前置通知,目标方法运行前执行
After Returning Advice 返回后通知,目标方法返回数值后执行
After Throwing Advice 异常通知,目标方法抛出异常后执行
After Advice 后置通知,目标方法运行后执行
Around Advice 最强大通知,自定通知执行时机,可决定目标方法是否运行
@Around("execution(* com.imooc..*Service.*(..))") 【通知 切面表达式】
    // @Before()   @After()   @AfterThrowing   @AfterReturning
    //ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行

用于创建建议的注释如下:

@Before 声明before建议。在调用实际方法之前将其应用。
@After 声明after建议。在调用实际方法之后并返回结果之前应用。
@AfterReturning 声明返回建议之后。在调用实际方法之后并返回结果之前应用。但是您可以在建议中获得结果值。
@Around 声明环绕建议。它在调用实际方法之前和之后应用。
@AfterThrowing 声明了throws建议。如果实际方法引发异常,则应用此方法。

让我们看看用于定义建议的xml元素:
Spring AOP AspectJ Xml配置示例 - Spring教程 - 菜鸟教程 (cainiaoplus.com)
**aop: before **在调用实际的业务逻辑方法之前应用。
**aop: after **在调用实际的业务逻辑方法之后应用。
**aop: 返回后**在调用实际的业务逻辑方法后应用。可用于拦截通知中的返回值。
**aop: around **在调用实际的业务逻辑方法之前和之后都将应用。
**aop: 投掷后**如果实际的业务逻辑方法抛出异常,则将其应用。

特殊的“通知” - 引介增强
  • 引介增强是对类的增强,而非方法
  • 引介增强允许再运行时为目标类增加新属性或方法
  • 引介增强允许再运行时改变类的行为,让类随运行环境动态变更
MethodAspect.java
    public void doAfter(JoinPoint joinPoint){
        System.out.println("<----触发后置通知");
    }
-----------------------------------------------
applicationContext.xml
<aop:after method="doAfter" pointcut-ref="pointcut"></aop:after>
MethodAspect.java
     public void doAfterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("<----返回后通知:" + ret);
    }
-----------------------------------------------
applicationContext.xml
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
    
MethodAspect.java
public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
        System.out.println("<----异常通知:" + th.getMessage());
    }
-----------------------------------------------
applicationContext.xml
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>

打印顺序根据applicationContext.xml代码排列顺序一致
applicationContext.xml [最终]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.imooc.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>
    <!-- AOP配置 迎接新MethodAspect切面的到来-->
    <bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"></bean>
    <aop:config>
        <!-- PointCut 切点,使用execution表达式描述切面的作用范围 -->
        <!-- execution(public * com.imooc..*.*(..)) 说明切面作用在com.imooc包下的所有类的所有方法上 -->
        <!--<aop:pointcut id="pointcut" expression="execution(public * com.imooc..*.*(..))"></aop:pointcut>-->
        <!--只对所有Service类生效-->
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"></aop:pointcut>
        <!--只对所有返回值为String类型方法生效-->
        <!--<aop:pointcut id="pointcut" expression="execution(String com.imooc..*Service.*(..))"></aop:pointcut>-->
        <!--对方法名进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.create*(..))"></aop:pointcut>-->
        <!-- 对参数进行约束 -->
        <!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(String,*))"></aop:pointcut>-->
        <!-- 定义切面类 -->
        <aop:aspect ref="methodAspect"> <!--关联一下-->
            <!-- before通知(Advice),代表在目标方法运行前先执行methodAspect.printExecutionTime() -->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
            <aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
            <aop:after method="doAfter" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>
SpringApplication.java
package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser(); //模拟创建新用户过程
        userService.generateRandomPassword("MD5", 15);
    }
}
===========================================================
---->2024-01-12 13:40:56 885:com.imooc.spring.aop.service.UserService.createUser
---->参数个数:0
<----异常通知:用户已存在
<----触发后置通知
Exception in thread "main" java.lang.RuntimeException: 用户已存在
    


@Before示例

在实际业务逻辑方法之前应用AspectJ Before Advice。您可以在此处执行任何操作,例如转换,身份验证等。

创建一个包含实际业务逻辑的类

Operation.java
package com.nhooo;
public  class Operation{
    public void msg(){System.out.println("msg method invoked");}
    public int m(){System.out.println("m method invoked");return 2;}
    public int k(){System.out.println("k method invoked");return 3;}
}

现在,创建包含在建议之前的方面类

TrackOperation.java
package com.nhooo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackOperation{
    //它将应用于Operation类的所有方法。
    @Pointcut("execution(* Operation.*(..))")
    public void k(){}//pointcut name
    
    @Before("k()")//在before通知上应用切入点
    public void myadvice(JoinPoint jp)//it is advice (before advice)
    {
        System.out.println("additional concern");
        //System.out.println("Method Signature: "  + jp.getSignature());
    }
}

现在创建定义bean的applicationContext.xml文件

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="opBean" class="com.nhooo.Operation">    </bean>
    <bean id="trackMyBean" class="com.nhooo.TrackOperation"></bean>
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"></bean>
</beans>

现在,让我们称为实际方法

Test.java
package com.nhooo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test{
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Operation e = (Operation) context.getBean("opBean");
        System.out.println("calling msg...");
        e.msg();
        System.out.println("calling m...");
        e.m();
        System.out.println("calling k...");
        e.k();
    }
}

===========================================================================
calling msg...
additional concern
msg() method invoked
calling m...
additional concern
m() method invoked
calling k...
additional concern
k() method invoked
apo:before示例

在实际业务逻辑方法之前应用”先行AspectJ建议”。您可以在此处执行任何操作,例如转换,身份验证等。
创建一个包含实际业务逻辑的类

Operation.java
package com.nhooo;
public  class Operation{
    public void msg(){System.out.println("msg method invoked");}
    public int m(){System.out.println("m method invoked");return 2;}
    public int k(){System.out.println("k method invoked");return 3;}
}

现在,创建包含在建议之前的方面类

TrackOperation.java
package com.nhooo;
import org.aspectj.lang.JoinPoint;
public class TrackOperation{
    public void myadvice(JoinPoint jp)//it is advice
    {
        System.out.println("additional concern");
        //System.out.println("Method Signature: "  + jp.getSignature());
    }
}

现在创建定义bean的applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:aspectj-autoproxy />
<bean id="opBean" class="com.nhooo.Operation">    </bean>
<bean id="trackAspect" class="com.nhooo.TrackOperation"></bean>
        
<aop:config>
  <aop:aspect id="myaspect" ref="trackAspect" >
     <!-- @Before -->
     <aop:pointcut id="pointCutBefore"    expression="execution(* com.nhooo.Operation.*(..))" />
     <aop:before method="myadvice" pointcut-ref="pointCutBefore" />
  </aop:aspect>
</aop:config>
    
</beans>

现在,让我们称为实际方法

Test.java
package com.nhooo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test{
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Operation e = (Operation) context.getBean("opBean");
        System.out.println("calling msg...");
        e.msg();
        System.out.println("calling m...");
        e.m();
        System.out.println("calling k...");
        e.k();
    }
}

===================================================================
calling msg...
additional concern
msg() method invoked
calling m...
additional concern
m() method invoked
calling k...
additional concern
k() method invoked

如您所见,在调用msg(),m()和k()方法之前,还会打印出其他问题。



@After示例

在调用实际的业务逻辑方法之后,应用了after建议之后的AspectJ。它可以用来维护日志,安全性,通知等。
在这里,我们假设 Operation.javaapplicationContext.xmlTest.java 文件与@Before示例中给出的文件相同。

TrackOperation.Java
package com.nhooo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackOperation{
    @Pointcut("execution(* Operation.*(..))")
    public void k(){}//pointcut name
    
    @After("k()")//applying pointcut on after advice
    public void myadvice(JoinPoint jp)//it is advice (after advice)
    {
        System.out.println("additional concern");
        //System.out.println("Method Signature: "  + jp.getSignature());
    }
}

===============================================================
calling msg...
msg() method invoked
additional concern
calling m...
m() method invoked
additional concern
calling k...
k() method invoked
additional concern
aop:after示例

在调用实际的业务逻辑方法之后,应用了通知之后的AspectJ。它可用于维护日志,安全性,通知等。
在这里,我们假设 **Operation.java **, **TrackOperation.java **和 **Test.java **文件与aop:before 中的示例相同。
现在创建定义bean的applicationContext.xml文件。

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:aspectj-autoproxy />
<bean id="opBean" class="com.nhooo.Operation">    </bean>
<bean id="trackAspect" class="com.nhooo.TrackOperation"></bean>
        
<aop:config>
  <aop:aspect id="myaspect" ref="trackAspect" >
     <!-- @After -->
     <aop:pointcut id="pointCutAfter"    expression="execution(* com.nhooo.Operation.*(..))" />
     <aop:after method="myadvice" pointcut-ref="pointCutAfter" />
  </aop:aspect>
</aop:config>
</beans>
calling msg...
msg() method invoked
additional concern
calling m...
m() method invoked
additional concern
calling k...
k() method invoked
additional concern

您可以看到在调用msg(),m()和k()方法之后,还会出现其他问题。



@AfterReturning示例

通过在返回建议后使用,我们可以在建议中获得结果。创建包含以下内容的类业务逻辑。

Operation.java
package com.nhooo;
public  class Operation{
    public int m(){System.out.println("m() method invoked");return 2;}
    public int k(){System.out.println("k() method invoked");return 3;}
}

创建返回建议后包含的方面类

TrackOperation.java
package com.nhooo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class TrackOperation{
    @AfterReturning(pointcut = "execution(* Operation.*(..))", returning= "result")
    
    public void myadvice(JoinPoint jp,Object result)  //it is advice (after returning advice)
    {
        System.out.println("additional concern");
        System.out.println("Method Signature: "  + jp.getSignature());
        System.out.println("Result in advice: "+result);
        System.out.println("end of after returning advice...");
    }
}

文件: applicationContext.xml 与@Before建议示例中给出的

文件: Test.java 现在创建调用实际方法的Test类。

Test.java
package com.nhooo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test{
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Operation e = (Operation) context.getBean("opBean");
        System.out.println("calling m...");
        System.out.println(e.m());
        System.out.println("calling k...");
        System.out.println(e.k());
    }
}
================================================================
calling m...
m() method invoked
additional concern
Method Signature: int com.nhooo.Operation.m()
Result in advice: 2
end of after returning advice...
2
calling k...
k() method invoked
additional concern
Method Signature: int com.nhooo.Operation.k()
Result in advice: 3
end of after returning advice...
3

环绕通知 [public Object check(ProceedingJoinPoint pjp)]

利用AOP进行方法性能筛查 [筛查哪个实现时间长 可以进行代码优化]
dao/UserDao.java
package com.imooc.spring.aop.dao;

/**
 * 用户表Dao
 */
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}

---------------------------------
dao/EmployeeDao.java
package com.imooc.spring.aop.dao;

/**
 * 员工表Dao
 */
public class EmployeeDao {
    public void insert(){
        System.out.println("新增员工数据");
    }
}
service/EmployeeService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.EmployeeDao;

/**
 * 员工服务
 */
public class EmployeeService {
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("执行员工入职业务逻辑");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}

---------------------------------
service/UserService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.UserDao;

/**
 * 用户服务
 */
public class UserService {
    private UserDao userDao;

    public void createUser(){
        try {
            Thread.sleep(3000); //沉睡 为了触发环绕通知
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行员工入职业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
aspect/MethodChecker.java
package com.imooc.spring.aop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MethodChecker {
    ////ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        try {
            long startTime = new Date().getTime();//方法执行前
            Object ret = pjp.proceed();//执行目标方法
            long endTime = new Date().getTime();//
            long duration = endTime - startTime; //方法执行后 执行时长
            if(duration >= 1000){//认为执行太慢了 打印信息
                String className = pjp.getTarget().getClass().getName();
                String methodName = pjp.getSignature().getName();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now = sdf.format(new Date());
                System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
            }
            return ret;//方法返回
        } catch (Throwable throwable) {
            System.out.println("Exception message:" + throwable.getMessage());
            throw throwable;
        }
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.imooc.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

    <bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"></bean>
    <aop:config> <!--所有类进行拦截-->
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"></aop:pointcut>
        <aop:aspect ref="methodChecker">
            <!--环绕通知-->
            <aop:around method="check" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>
SpringApplication.java
package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();
    }
}

==============================================================
执行员工入职业务逻辑
新增用户数据
=======2024-01-12 13:52:41 475:com.imooc.spring.aop.service.UserService.createUser(3010ms)======

Process finished with exit code 0


@Around示例

围绕通知的AspectJ在调用实际的业务逻辑方法之前和之后都得到应用。在这里,我们是假设 applicationContext.xml 文件与@Before示例中给出的文件相同。创建一个包含实际业务逻辑的类。

Operation.java
package com.nhooo;
public class Operation{
    public void msg(){System.out.println("msg() is invoked");}
    public void display(){System.out.println("display() is invoked");}
}

创建包含围绕建议的方面类。您需要在advice方法中传递 PreceedingJoinPoint 引用,以便我们可以通过调用proce来进行请求()方法。文件: TrackOperation.java

TrackOperation.java
package com.nhooo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackOperation
{
    @Pointcut("execution(* Operation.*(..))")
    public void abcPointcut(){}
    
    @Around("abcPointcut()")
    public Object myadvice(ProceedingJoinPoint pjp) throws Throwable 
    {
        System.out.println("Additional Concern Before calling actual method");
        Object obj=pjp.proceed();
        System.out.println("Additional Concern After calling actual method");
        return obj;
    }
}

现在创建调用实际方法的Test类。

Test.java
package com.nhooo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test{
    public static void main(String[] args){
        ApplicationContext context = new classPathXmlApplicationContext("applicationContext.xml");
        Operation op = (Operation) context.getBean("opBean");
        op.msg();
        op.display();
    }
}

================================================================
Additional Concern Before calling actual method
msg() is invoked
Additional Concern After calling actual method
Additional Concern Before calling actual method
display() is invoked
Additional Concern After calling actual method

基于注解配置Spring AOP

aspect/MethodChecher.java
package com.imooc.spring.aop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
@Component //标记当前类为组件
@Aspect //说明当前类是切面类
public class MethodChecker {
    //环绕通知,参数为PointCut切点表达式
    @Around("execution(* com.imooc..*Service.*(..))")
    // @Before()   @After()   @AfterThrowing   @AfterReturning
    //ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        try {
            long startTime = new Date().getTime();
            Object ret = pjp.proceed();//执行目标方法
            long endTime = new Date().getTime();
            long duration = endTime - startTime; //执行时长
            if(duration >= 1000){
                String className = pjp.getTarget().getClass().getName();
                String methodName = pjp.getSignature().getName();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now = sdf.format(new Date());
                System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
            }
            return ret;
        } catch (Throwable throwable) {
            System.out.println("Exception message:" + throwable.getMessage());
            throw throwable;
        }
    }
}
dao/UserDao
package com.imooc.spring.aop.dao;

import org.springframework.stereotype.Repository;

/**
 * 用户表Dao
 */
@Repository
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}
----------------------------------------------
dao/EmployeeDao.java
package com.imooc.spring.aop.dao;

import org.springframework.stereotype.Repository;

/**
 * 员工表Dao
 */
@Repository
public class EmployeeDao {
    public void insert(){
        System.out.println("新增员工数据");
    }
}
service/UserService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.UserDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 用户服务
 */
@Service
public class UserService {
    @Resource
    private UserDao userDao;

    public void createUser(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行员工入职业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
---------------------------------------------------------
service/EmployeeService.java
package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.EmployeeDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 员工服务
 */
@Service
public class EmployeeService {
    @Resource
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("执行员工入职业务逻辑");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}
SpringApplication.java
package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();
    }
}
========================================================================
执行员工入职业务逻辑
新增用户数据
=======2024-01-12 17:06:18 669:com.imooc.spring.aop.service.UserService.createUser(3015ms)======

Process finished with exit code 0
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--初始化IoC容器-->
    <context:component-scan base-package="com.imooc"/>
  ★  <!--启用Spring AOP注解模式--> ★
    <aop:aspectj-autoproxy/>
</beans>
增加了是说明这个类是需要被IoC实例化的
  • 四种组件类型注解-声明当前类的功能与职责 【回忆上面的知识点】
注解 说明
@Component 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化
@Controller 语义注解,说明当前类是MVC应用中的控制器类
@Service 语义注解,说明当前类是Service业务服务类
@Repository 语义注解,说明当前类用于业务持久层,通常描述对应Dao类

Spring AOP实现原理

★★★★★ 面试 Spring AOP 底层的实现原理 ★★★★★★
  • Spring基于代理模式实现功能动态扩展,包含两种形式:
    • 目标类拥有接口,通过JDK动态代理实现功能扩展 来实现目标类的代理从而实现扩展
    • 目标类没有接口,通过CGLib组件实现功能扩展 通过继承的方式实现扩展
代理模式
  • 代理模式通过代理对象对原对象的实现功能扩展

基于UserService接口实现代理类 同时持有与之对应的具体实现

静态代理 [每一个具体的委托类都要手动创造一个代理类]
aop/service/UserService [接口]
package com.imooc.spring.aop.service;

public interface UserService {
    public void createUser();
}
-------------------------------------
aop/service/UserServiceImpl.java
package com.imooc.spring.aop.service;

public class UserServiceImpl implements UserService{
    public void createUser() {
        System.out.println("执行创建用户业务逻辑");
    }
}
-------------------------------------
aop/service/UserServiceProxy.java [代理]
package com.imooc.spring.aop.service;

import java.text.SimpleDateFormat;
import java.util.Date;
//静态代理是指必须手动创建代理类的代理模式使用方式
public class UserServiceProxy implements UserService{
    //持有委托类的对象
    private UserService userService;
    public UserServiceProxy(UserService userService){
        this.userService = userService;
    }

    public void createUser() {
        System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
        userService.createUser();
    }
}
-------------------------------------
aop/service/UserServiceProxy1.java [代理1]
package com.imooc.spring.aop.service;

public class UserServiceProxy1 implements UserService{
    private UserService userService ;
    public UserServiceProxy1(UserService userService){
        this.userService = userService;
    }

    public void createUser() {
        userService.createUser();
        System.out.println("========后置扩展功能======");
    }
}
package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import com.imooc.spring.aop.service.UserServiceImpl;
import com.imooc.spring.aop.service.UserServiceProxy;
import com.imooc.spring.aop.service.UserServiceProxy1;

public class Application {
    public static void main(String[] args) { //类似于二房东
        UserService userService = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
        userService.createUser();
    }
}

AOP底层逻辑—JDK动态代理

aop/service.UserService.java [接口]
package com.imooc.spring.aop.service;

public interface UserService {
    public void createUser();
}


-------------------------------------
aop/service.EmployeeService.java [接口]
package com.imooc.spring.aop.service;

public interface EmployeeService {
    public void createEmployee();
}
aop/service.EmployeeServiceImpl.java
package com.imooc.spring.aop.service;

public class EmployeeServiceImpl implements EmployeeService {
    public void createEmployee() {
        System.out.println("执行创建员工业务逻辑");
    }
}
--------------------------------------
aop/service.UserServiceImpl.java
package com.imooc.spring.aop.service;

public class UserServiceImpl implements UserService{
    public void createUser() {
        System.out.println("执行创建用户业务逻辑");
    }
}
ProxyInvocationHandler.java
package com.imooc.spring.aop.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强
 * InvocationHandler实现类与切面类的环绕通知类似
 */
public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;//目标对象
    private ProxyInvocationHandler(Object target){
        this.target = target;
    }
    /**
     * 在invoke()方法对目标方法进行增强 反射method方法的时候遇见过invoke
     * @param proxy 代理类对象
     * @param method 目标方法对象
     * @param args 目标方法实参
     * @return 目标方法运行后返回值
     * @throws Throwable 目标方法抛出的异常
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
        Object ret = method.invoke(target, args);//调用目标方法,ProceedingJoinPoint.proceed()
        return ret;
    }

    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
        //动态创建代理类
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                invocationHandler);
        userServiceProxy.createUser();

        //动态代理,必须实现接口才可以运行
        EmployeeService employeeService = new EmployeeServiceImpl();
        EmployeeService employeeServiceProxy = (EmployeeService)Proxy.newProxyInstance(employeeService.getClass().getClassLoader(),
                employeeService.getClass().getInterfaces(),
                new ProxyInvocationHandler(employeeService));
        employeeServiceProxy.createEmployee();
    }
}

=======================================================

=====2024-01-12 19:52:34 179=========
执行创建用户业务逻辑
=====2024-01-12 19:52:34 181=========
执行创建员工业务逻辑

Process finished with exit code 0

AOP底层逻辑—CGLib实现代理类

  • CGLib是运行时字节码增强技术
  • Spring AOP扩展无接口类使用CGLib
  • AOP会运行时生成目标继承类字节码的方式进行行为扩展

Spring JDBC与事务管理

Spring JDBC
  • Spring JDBC是Spring框架用于处理关系型数据库的模块
  • Spring JDBC对JDBC API进行封装,极大简化开发工作量
  • JdbcTemplate是Spring JDBC核心类, 提供数据CRUD方法
为什么有了Mybatis还需要Spring JDBC?

因为这两者面向的对象是不一样的
mybatis 封装程度高适合中小企业敏捷开发, 快速完成与数据库交互的工作, 封装程度高 执行效率低【慢】
Spring JDBC只是对原始的JDBC的API进行了简单封装 大厂使用【轻量级】可二次封装

Spring JDBC的使用步骤

  • Maven工程引入依赖spring-jdbc

  • applicationContext.xml配置DataSource数据源 [用于指向连接哪数据库的哪种服务器 账号密码都是啥]

  • 在Dao注入JdbcTemplate对象, 实现数据CRUD

JdbcTemplate类

Spring JdbcTemplate类的方法
方法 说明
public int update(String query) 用于插入,更新和删除记录。
public int update(String query,Object … args) 用于通过使用给定参数的PreparedStatement插入,更新和删除记录。
public void execute(String query) 用于执行DDL查询。
public T execute(String sql, PreparedStatementCallback action) 通过使用PreparedStatement回调执行查询。
public T query(String sql, ResultSetExtractor rse) 用于使用ResultSetExtractor获取记录。
public List query(String sql, RowMapper rse) 用于使用RowMapper获取记录。

JdbcTemplate实现增删改查

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc.spring</groupId>
    <artifactId>jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
    </dependencies>

</project>
applicationContext.xml [SpringIoC基础配置文件]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 数据源设置:使用哪种服务器 哪种数据源 账号密码是什么 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
<!--    创建数据库连接 找数据源    JdbcTemplate提供数据CRUD的API 让IoC容器初始化的时候自动实例化jdbcTemplate 实例化的过程中要将设置好的数据源dataSource注入到jdbcTemplate属性中 此时就完成了实例化-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="employeeDao" class="com.imooc.spring.jdbc.dao.EmployeeDao">
<!--    为Dao注入JdbcTemplate对象 -->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>
com/imooc/spring/jdbc/entity/Employee.java
public class Employee {
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                ", hiredate=" + hiredate +
                '}';
    }
} +Setter Getter
com/imooc/spring/jdbc/dao/EmployeeDao.java
package com.imooc.spring.jdbc.dao;

import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

public class EmployeeDao {
/*    依赖与jdbcTemplate 要持有其
* <!--    创建数据库连接 找数据源    JdbcTemplate提供数据CRUD的API-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
* */
    private JdbcTemplate jdbcTemplate;
    public Employee findById(Integer eno){
//      //将唯一转换的数据返回对应的对象 jdbcTemplate为了运行时的动态注入
        String sql = "select * from employee where eno=?";
        //将bean属性与每一行的一一对应   数据库记录到实体对象的转换
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return employee;
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
SpringApplication.java
package com.imooc.spring.jdbc;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        EmployeeDao employeeDao = context.getBean("employeeDao", EmployeeDao.class);
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);

    }
}

=======================================================
Employee{eno=3308, ename='张三', salary=6000.0, dname='研发部', hiredate=2011-05-08 00:00:00.0}

Process finished with exit code 0

JdbcTemplate的数据查询方法

com/imooc/spring/jdbc/dao/EmployeeDao.java
package com.imooc.spring.jdbc.dao;

import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class EmployeeDao {
/*    依赖与jdbcTemplate 要持有其
* <!--    创建数据库连接 找数据源    JdbcTemplate提供数据CRUD的API-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
* */
    private JdbcTemplate jdbcTemplate;
    public Employee findById(Integer eno){
//      //将唯一转换的数据返回对应的对象 jdbcTemplate为了运行时的动态注入
        String sql = "select * from employee where eno=?";
        //将bean属性与每一行的一一对应   数据库记录到实体对象的转换
        //查询单条数据 转换成对象
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return employee;
    }
    public List<Employee> findByDname(String dname){
        String sql = "select * from employee where dname = ?";
        //查询复合数据                             sql  对应的参数数组  转换的Mapper对象
        //多条记录 每一条记录转换为一个实体对象 放入List集合中进行返回
        List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return list;
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
JdbcTemplateTestor.java
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
    @Resource
    private EmployeeDao employeeDao;

    @Test
    public void testFindById(){
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);
    }

    @Test
    public void testFindByDname(){
        System.out.println(employeeDao.findByDname("yan'fa'bu"));
    }
}
================================================================
[Employee{eno=3308, ename='张三', salary=6000.0, dname='研发部', hiredate=2011-05-08 00:00:00.0}, Employee{eno=3420, ename='李四', salary=8700.0, dname='研发部', hiredate=2006-11-11 00:00:00.0}]


Process finished with exit code 0
★★ 新增 ★★
在没有对应的实体类情况下也可以得到相应的结果 结果被封装成为了Map对象
EmployeeDao.java
//    按列表返回每一条按Map对象列表返回
    public List findMapByDname(String dname){
        String sql = "select eno as empno, salary as a from employee where dname = ?";
        //将查询结果作为Map进行封装
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname});
        return maps;
    }


JdbcTemplateTestor.java
@Test
    public void testFindMapByDname(){
        System.out.println(employeeDao.findMapByDname("研发部"));
    }
================================================================
[{empno=3308, a=6000.0}, {empno=3420, a=8700.0}]

Process finished with exit code 0
总结  [★★★★ queryForObject query queryForList ★★★★]
 private JdbcTemplate jdbcTemplate;
    public Employee findById(Integer eno){
//      //将唯一转换的数据返回对应的对象 jdbcTemplate为了运行时的动态注入
        String sql = "select * from employee where eno=?";
        //将bean属性与每一行的一一对应   数据库记录到实体对象的转换
        // ★★查询单条数据 转换成对象 ★★
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return employee;
    }

    public List<Employee> findByDname(String dname){
        String sql = "select * from employee where dname = ?";
        // ★★查询复合数据★★                      sql  对应的参数数组  转换的Mapper对象
        // ★★多条记录 每一条记录转换为一个实体对象★★ 放入List集合中进行返回
        List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return list;
    }

//    按列表返回每一条按Map对象列表返回
//特殊情况:无法进行实体类的映射则可用直接用queryForList特殊返回Map 来完成对应数据的封装
    public List findMapByDname(String dname){
        String sql = "select eno as empno, salary as a from employee where dname = ?";
        //将查询结果作为Map进行封装
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname});
        return maps;
    }

JdbcTemplate实现增删改查

★★ 增 ★★
EmployeeDao.java
public void insert(Employee employee){
        String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)";
        jdbcTemplate.update(sql,new Object[]{
                employee.getEno(), employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate()
        });
    }

JdbcTemplateTestor.java
@Test
    public void testinsert(){
        Employee employee = new Employee();
        employee.setEno(8888);
        employee.setEname("赵六");
        employee.setSalary(6666f);
        employee.setDname("研发部");
        employee.setHiredate(new Date());
        employeeDao.insert(employee);
    }


★★ 更新 ★★
EmployeeDao.java
public int update(Employee employee){
        String sql = "UPDATE employee SET ename=?, salary=?, dname=?, hiredate=? WHERE eno=?";
        int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()});
        return count;
    }

JdbcTemplateTestor.java
@Test
    public void testUpdate() {
        Employee employee = employeeDao.findById(8888);
        employee.setSalary(employee.getSalary() + 1000);
        int count = employeeDao.update(employee);
        System.out.println("本次更新" + count + "条数据");
    }
=========================================================
本次更新1条数据

Process finished with exit code 0


★★ 删除 ★★
EmployeeDao.java
public int delete(Integer eno){
        String sql = "delete from employee where eno=?";
        int update = jdbcTemplate.update(sql, new Object[]{eno});
        return update;
    }

JdbcTemplateTestor.java
@Test
    public void testDelete() {
        int count = employeeDao.delete(8888);
        System.out.println("本次删除" + count + "条数据");
    }
=========================================================
本次删除1条数据

事务

  • 事务式以一种可靠、一致的方法,访问和操作数据库的程序单元
  • 说人话:要么把事情做完,要么什么都不做,不要做一半
  • 事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带

编程式事务 [手动控制啥时候提交 啥时候回滚]

  • 编程式事务是指通过代码手动提交回滚事务的事务控制方法
  • SpringJDBC通过TransactionManager事务管理器实现事务控制
  • 事务管理器提供commit/rollback方法进行事务提交与回滚

S1需求:公司来了10名新员工 批量导入员工表中 (要么什么都不做,要么把事情做完) 【类似银行存钱】

com/imooc/spring/jdbc/service/EmployeeService.java
package com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;

import java.util.Date;

public class EmployeeService {
    private EmployeeDao employeeDao;
    public void bathImport(){
        for (int i = 0; i < 10; i++) {
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("员工" + i);
            employee.setSalary(4000f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}



applicationContext.xml
<bean id="employeeService" class="com.imooc.spring.jdbc.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

    

JdbcTemplateTestor.java
@Resource
    private EmployeeService employeeService;
@Test
    public void testBatchImport(){
        employeeService.bathImport();
        System.out.println("批量导入成功");
    }

此方法是一条一条的增加写入sql操作
09:47:33.565 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
09:47:33.565 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)]
09:47:33.567 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
09:47:33.567 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true]
09:47:33.684 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQLWarning ignored: SQL state '22007', error code '1292', message [Incorrect date value: '2024-01-14 09:47:33.564' for column 'hiredate' at row 1]
09:47:33.690 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
09:47:33.690 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)]
09:47:33.690 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
09:47:33.690 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true]
09:47:33.695 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQLWarning ignored: SQL state '22007', error code '1292', message [Incorrect date value: '2024-01-14 09:47:33.69' for column 'hiredate' at row 1]
09:47:33.695 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update


编程式事务-2 [放入事务区统一管理]

com/imooc/spring/jdbc/service/EmployeeService.java
package com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.Date;

public class EmployeeService {
    private EmployeeDao employeeDao;
    private DataSourceTransactionManager transactionManager;
    public void bathImport(){
        //定义了事务默认的标准配置
        TransactionDefinition definition = new DefaultTransactionDefinition();
        //开始一个事务 放在事务区中统一进行管理
        TransactionStatus status = transactionManager.getTransaction(definition);

        try {
            for (int i = 0; i < 10; i++) {
                if (i==3){
                    throw new RuntimeException("意料之外的异常");
                }
                Employee employee = new Employee();
                employee.setEno(8010 + i);
                employee.setEname("员工" + i);
                employee.setSalary(4000f);
                employee.setDname("市场部");
                employee.setHiredate(new Date());
                employeeDao.insert(employee);
            }
            //提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            //回滚事务
            transactionManager.rollback(status);
            e.printStackTrace();
        }
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public DataSourceTransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(DataSourceTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 数据源设置:使用哪种服务器 哪种数据源 账号密码是什么 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--    创建数据库连接 找数据源    JdbcTemplate提供数据CRUD的API-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="employeeDao" class="com.imooc.spring.jdbc.dao.EmployeeDao">
        <!--    为Dao注入JdbcTemplate对象 -->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="employeeService" class="com.imooc.spring.jdbc.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
<!--     将属性进行注入 要添加其getter setter-->
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

<!--    事务管理器 数据源  [整体提交以及回滚] 需要控制的类中注入bean id="transactionManager" -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>
JdbcTemplateTestor.java
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.imooc.spring.jdbc.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private EmployeeService employeeService;

    @Test
    public void testFindById() {
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);
    }

    @Test
    public void testFindByDname() {
        System.out.println(employeeDao.findByDname("市场部"));
    }

    @Test
    public void testFindMapByDname() {
        System.out.println(employeeDao.findMapByDname("研发部"));
    }

    @Test
    public void testinsert() {
        Employee employee = new Employee();
        employee.setEno(8888);
        employee.setEname("赵六");
        employee.setSalary(6666f);
        employee.setDname("研发部");
        employee.setHiredate(new Date());
        employeeDao.insert(employee);
    }

    @Test
    public void testUpdate() {
        Employee employee = employeeDao.findById(8888);
        employee.setSalary(employee.getSalary() + 1000);
        int count = employeeDao.update(employee);
        System.out.println("本次更新" + count + "条数据");
    }

    @Test
    public void testDelete() {
        int count = employeeDao.delete(8888);
        System.out.println("本次删除" + count + "条数据");
    }

    @Test
    public void testBatchImport(){
        employeeService.bathImport();
        System.out.println("批量导入成功");
    }
}

========================================================
都是在一个数据库连接里操作的
10:01:16.165 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
10:01:16.165 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@c88a337]
10:01:16.166 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@c88a337] after transaction
java.lang.RuntimeException: 意料之外的异常
    at com.imooc.spring.jdbc.service.EmployeeService.bathImport(EmployeeService.java:24)
    at JdbcTemplateTestor.testBatchImport(JdbcTemplateTestor.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

★★★ 编程式事务 ★★★
优点:容易理解 编程快
缺点:容易受到人为因素影响 结果致命

声明式事务

  • 声明式事务指在不修改源代码情况下通过配置形式自动实现事务控制, 声明式事务本质就是AOP环绕通知
  • 当目标方法执行成功时, 自动提交事务
  • 当目标方法抛出运行异常时, 自动事务回滚
配置过程
  • 配置TransactionManager事务管理器
  • 配置事务通知与事务属性
  • 为事务通知绑定PointCut切点

需求:在不修改原始代码的情况下 配置Service里面的声明式事务

pom.xml
<!--logback日志组件,Spring框架默认集成-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
applicationContext.xml
<!--    1-3具体含义:当我们目标方法是imooc包下Service 且方法名为batchImport的时候 则认为当前方法需要使用事务-->
<!--    利用transactionManager来完成对事务的提交以及回滚-->

    <!-- 1.事务管理器,用于创建事务/提交/回滚 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2.事务通知配置,决定哪些方法使用事务,哪些方法不使用事务 advice是通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
 <!-- ★★★★ 目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚 propagation="REQUIRED"需要使用  propagation是事务传播行为★★★★-->
            <tx:method name="batchImport" propagation="REQUIRED"/>
            <tx:method name="batch*" propagation="REQUIRED"/>
            <!-- 设置所有findXXX方法不需要使用事务 不支持事务-->
            <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--3. 定义声明式事务的作用范围-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
EmployeeService.java
vpackage com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;

import java.util.Date;

public class EmployeeService {
    private EmployeeDao employeeDao;
    private BatchService batchService;

    public void batchImport() {
        for (int i = 1; i <= 10; i++) {
            //注意噢 这里是被注释的
//            if(i==3){
//                throw new RuntimeException("意料之外的异常");
//            }
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("员工" + i);
            employee.setSalary(4000f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public BatchService getBatchService() {
        return batchService;
    }

    public void setBatchService(BatchService batchService) {
        this.batchService = batchService;
    }
}
EmployeeDao.java 未改变
JdbcTemplateTestor.java
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.imooc.spring.jdbc.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private EmployeeService employeeService;

    @Test
    public void testFindById(){
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);
    }

    @Test
    public void testFindByDname(){
        System.out.println(employeeDao.findByDname("市场部"));
    }

    @Test
    public void testFindMapByDname(){
        System.out.println(employeeDao.findMapByDname("研发部"));
    }

    @Test
    public void testInsert(){
        Employee employee = new Employee();
        employee.setEno(8888);
        employee.setEname("赵六");
        employee.setSalary(6666f);
        employee.setDname("研发部");
        employee.setHiredate(new Date());
        employeeDao.insert(employee);
    }

    @Test
    public void  testUpdate(){
        Employee employee = employeeDao.findById(8888);
        employee.setSalary(employee.getSalary() + 1000);
        int count = employeeDao.update(employee);
        System.out.println("本次更新" + count + "条数据");
    }

    @Test
    public void  testDelete(){
        int count = employeeDao.delete(8888);
        System.out.println("本次删除" + count + "条数据");
    }

    @Test
    public void testBatchImport(){
        employeeService.batchImport();
        System.out.println("批量导入成功");
    }

    @Test
    public void testStartImportJob(){
        employeeService.startImportJob();
    }
}

事务传播行为【面试】

  • 事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式
  • XML:<tx:method name="..." propagation="REQUIRED"/>
  • 注解: @Transactional(propagation=Propagation.REQUIRED)

加个BatchService 然后在EmployeeService加一个private BatchService batchService;(getter + setter) 再次插入一个public void startImportJob()
进行applicationContext.xml的bean注入


会被声明式事务范围包裹噢
<aop:pointcut id=”pointcut” expression=”execution(* com.imooc..*Service.*(..))”/>

applicationContext.xml
加入 importJob1 importJob2
<tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚 propagation="REQUIRED"需要使用-->
            <tx:method name="batchImport" propagation="REQUIRED"/>
            <tx:method name="batch*" propagation="REQUIRED"/>
            <!-- 设置所有findXXX方法不需要使用事务 -->
            <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>

            <tx:method name="importJob1" propagation="REQUIRES_NEW"/>
            <tx:method name="importJob2" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

补充public void startImportJob()

public void startImportJob(){
    //会有关联行为 若2失败则无法导入 1也会回滚
        batchService.importJob1();
        if(1==1){
            throw new RuntimeException("意料之外的异常");
        }
        batchService.importJob2();
        System.out.println("批量导入成功");
    }

//方法: REQUIRES_NEW 不同方法运行在不同事务中 
//事务1开始 事务1挂起事务2开始 事务2提交 事务1恢复 事务1挂起事务3开始 事务3提交事务1恢复 事务1提交
          <tx:method name="importJob1" propagation="REQUIRES_NEW"/>
            <tx:method name="importJob2" propagation="REQUIRES_NEW"/>
上面的分这写
com/imooc/spring/jdbc/service/BatchService.java
package com.imooc.spring.jdbc.service;
 
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;

import java.util.Date;
public class BatchService {

    private EmployeeDao employeeDao;

    public void importJob1(){
        for (int i = 1; i <= 10; i++) {
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("研发部员工" + i);
            employee.setSalary(4000f);
            employee.setDname("研发部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public void importJob2(){
        for (int i = 1; i <= 10; i++) {
            Employee employee = new Employee();
            employee.setEno(9000 + i);
            employee.setEname("市场部员工" + i);
            employee.setSalary(4500f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}
com/imooc/spring/jdbc/service/EmployeeService.java
package com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;

import java.util.Date;

public class EmployeeService {
    private EmployeeDao employeeDao;
    private BatchService batchService;

    public void batchImport() {
        for (int i = 1; i <= 10; i++) {
//            if(i==3){
//                throw new RuntimeException("意料之外的异常");
//            }
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("员工" + i);
            employee.setSalary(4000f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public void startImportJob(){
        batchService.importJob1();
        if(1==1){
            throw new RuntimeException("意料之外的异常");
        }
        batchService.importJob2();
        System.out.println("批量导入成功");
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public BatchService getBatchService() {
        return batchService;
    }

    public void setBatchService(BatchService batchService) {
        this.batchService = batchService;
    }
}
com/imooc/spring/jdbc/dao/EmployeeDao.java
package com.imooc.spring.jdbc.dao;

import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
public class EmployeeDao {
    private JdbcTemplate jdbcTemplate;

    public Employee findById(Integer eno){
        String sql = "select * from employee where eno = ?";
        //查询单条数据
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return employee;
    }

    public List<Employee> findByDname(String dname){
        String sql = "select * from employee where dname = ?";
        //查询复合数据
        List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return list;
    }

    public List<Map<String, Object>> findMapByDname(String dname){
        String sql = "select eno as empno , salary as s from employee where dname = ?";
        //将查询结果作为Map进行封装
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname});
        return maps;
    }

    public void insert(Employee employee){
        String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)";
        //利用update方法实现数据写入操作
        jdbcTemplate.update(sql,new Object[]{
           employee.getEno() , employee.getEname(),employee.getSalary(),employee.getDname() , employee.getHiredate()
        });
    }

    public int update(Employee employee){
        String sql = "UPDATE employee SET ename = ?, salary = ?, dname = ?, hiredate = ? WHERE eno = ?";
        int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()});
        return count;
    }

    public int delete(Integer eno){
        String sql = "delete from employee where eno = ?";
        return jdbcTemplate.update(sql, new Object[]{eno});
    }


    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--JdbcTemplate提供数据CRUD的API-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="employeeDao" class="com.imooc.spring.jdbc.dao.EmployeeDao">
        <!--为Dao注入JdbcTemplate对象-->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="employeeService" class="com.imooc.spring.jdbc.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
        <property name="batchService" ref="batchService"/>
    </bean>

    <bean id="batchService" class="com.imooc.spring.jdbc.service.BatchService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

<!--    1-3具体含义:当我们目标方法是imooc包下Service 且方法名为batchImport的时候 则认为当前方法需要使用事务-->
<!--    利用transactionManager来完成对事务的提交以及回滚-->

    <!-- 1.事务管理器,用于创建事务/提交/回滚 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2.事务通知配置,决定哪些方法使用事务,哪些方法不使用事务 advice是通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚 propagation="REQUIRED"需要使用-->
            <tx:method name="batchImport" propagation="REQUIRED"/>
            <tx:method name="batch*" propagation="REQUIRED"/>
            <!-- 设置所有findXXX方法不需要使用事务 -->
            <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>

            <tx:method name="importJob1" propagation="REQUIRES_NEW"/>
            <tx:method name="importJob2" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--3. 定义声明式事务的作用范围-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
jdbcTemplateTestor.java
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.imooc.spring.jdbc.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private EmployeeService employeeService;

    @Test
    public void testFindById(){
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);
    }

    @Test
    public void testFindByDname(){
        System.out.println(employeeDao.findByDname("市场部"));
    }

    @Test
    public void testFindMapByDname(){
        System.out.println(employeeDao.findMapByDname("研发部"));
    }

    @Test
    public void testInsert(){
        Employee employee = new Employee();
        employee.setEno(8888);
        employee.setEname("赵六");
        employee.setSalary(6666f);
        employee.setDname("研发部");
        employee.setHiredate(new Date());
        employeeDao.insert(employee);
    }

    @Test
    public void  testUpdate(){
        Employee employee = employeeDao.findById(8888);
        employee.setSalary(employee.getSalary() + 1000);
        int count = employeeDao.update(employee);
        System.out.println("本次更新" + count + "条数据");
    }

    @Test
    public void  testDelete(){
        int count = employeeDao.delete(8888);
        System.out.println("本次删除" + count + "条数据");
    }

    @Test
    public void testBatchImport(){
        employeeService.batchImport();
        System.out.println("批量导入成功");
    }

    @Test
    public void testStartImportJob(){
        employeeService.startImportJob();
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc.spring</groupId>
    <artifactId>jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <!--logback日志组件,Spring框架默认集成-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

</project>

注解配置声明式事务

//声明式事务核心注解
//放在类上,将声明式事务配置应用于当前类所有方法,默认事务传播为 REQUIRED
@Transactional(propagation = Propagation.REQUIRED) //不写也默认
写在类上就是所有 
写在方法就是单独方法运用
com/imooc/spring/jdbc/dao/EmployeeDao.java
package com.imooc.spring.jdbc.dao;

import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Repository
public class EmployeeDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public Employee findById(Integer eno){
        String sql = "select * from employee where eno = ?";
        //查询单条数据
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return employee;
    }

    public List<Employee> findByDname(String dname){
        String sql = "select * from employee where dname = ?";
        //查询复合数据
        List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class));
        return list;
    }

    public List<Map<String, Object>> findMapByDname(String dname){
        String sql = "select eno as empno , salary as s from employee where dname = ?";
        //将查询结果作为Map进行封装
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname});
        return maps;
    }

    public void insert(Employee employee){
        String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)";
        //利用update方法实现数据写入操作
        jdbcTemplate.update(sql,new Object[]{
           employee.getEno() , employee.getEname(),employee.getSalary(),employee.getDname() , employee.getHiredate()
        });
    }

    public int update(Employee employee){
        String sql = "UPDATE employee SET ename = ?, salary = ?, dname = ?, hiredate = ? WHERE eno = ?";
        int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()});
        return count;
    }

    public int delete(Integer eno){
        String sql = "delete from employee where eno = ?";
        return jdbcTemplate.update(sql, new Object[]{eno});
    }


    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
com/imooc/spring/jdbc/entity/Employee.java
public class Employee {
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;
} getter+setter
     @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                ", hiredate=" + hiredate +
                '}';
    }
com/imooc/spring/jdbc/service/BatchService.java
package com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.sun.xml.internal.ws.developer.Serialization;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
@Service
//不需要使用事务 只读操作
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class BatchService {
    @Resource
    private EmployeeDao employeeDao;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void importJob1(){
        for (int i = 1; i <= 10; i++) {
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("研发部员工" + i);
            employee.setSalary(4000f);
            employee.setDname("研发部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void importJob2(){
        for (int i = 1; i <= 10; i++) {
            Employee employee = new Employee();
            employee.setEno(9000 + i);
            employee.setEname("市场部员工" + i);
            employee.setSalary(4500f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}
com/imooc/spring/jdbc/service/EmployeeService.java
package com.imooc.spring.jdbc.service;

import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
@Service
//声明式事务核心注解
//放在类上,将声明式事务配置应用于当前类所有方法,默认事务传播为 REQUIRED
@Transactional(propagation = Propagation.REQUIRED) //不写也默认
public class EmployeeService {
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private BatchService batchService;

    @Transactional(propagation = Propagation.NOT_SUPPORTED , readOnly = true)
    public Employee findById(Integer eno){
        return employeeDao.findById(eno);
    }

    public void batchImport() {
        for (int i = 1; i <= 10; i++) {
            if(i==3){
                throw new RuntimeException("意料之外的异常");
            }
            Employee employee = new Employee();
            employee.setEno(8000 + i);
            employee.setEname("员工" + i);
            employee.setSalary(4000f);
            employee.setDname("市场部");
            employee.setHiredate(new Date());
            employeeDao.insert(employee);
        }
    }

    public void startImportJob(){
        batchService.importJob1();
        if(1==1){
            throw new RuntimeException("意料之外的异常");
        }
        batchService.importJob2();
        System.out.println("批量导入成功");
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    public BatchService getBatchService() {
        return batchService;
    }

    public void setBatchService(BatchService batchService) {
        this.batchService = batchService;
    }
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.imooc"/>
    <!--数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 启用注解形式声明式事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc.spring</groupId>
    <artifactId>jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <!--logback日志组件,Spring框架默认集成-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

</project>
test/java/JdbcTemplateTestor.java
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.imooc.spring.jdbc.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private EmployeeService employeeService;

    @Test
    public void testFindById(){
        Employee employee = employeeDao.findById(3308);
        System.out.println(employee);
    }

    @Test
    public void testFindByDname(){
        System.out.println(employeeDao.findByDname("市场部"));
    }

    @Test
    public void testFindMapByDname(){
        System.out.println(employeeDao.findMapByDname("研发部"));
    }

    @Test
    public void testInsert(){
        Employee employee = new Employee();
        employee.setEno(8888);
        employee.setEname("赵六");
        employee.setSalary(6666f);
        employee.setDname("研发部");
        employee.setHiredate(new Date());
        employeeDao.insert(employee);
    }

    @Test
    public void  testUpdate(){
        Employee employee = employeeDao.findById(8888);
        employee.setSalary(employee.getSalary() + 1000);
        int count = employeeDao.update(employee);
        System.out.println("本次更新" + count + "条数据");
    }

    @Test
    public void  testDelete(){
        int count = employeeDao.delete(8888);
        System.out.println("本次删除" + count + "条数据");
    }

    @Test
    public void testBatchImport(){
        employeeService.batchImport();
        System.out.println("批量导入成功");
    }

    @Test
    public void testStartImportJob(){
        employeeService.startImportJob();
    }
}
阅读全文

四级作文(新)

2023/11/19

作文评分原则

内容:50% 结构:10% 语言:40%
  1. 充实文章的内容:七种方法
  2. 固定文章的结构:总分总
  3. 丰富文章的语言:读范文;讲句型;躲错误;备模板

特点一:内容充实

expert testimony 专家证言法

declare 宣称,声称 (替代think)
claim (替代think)
exclaim 大叫(shout , yell)

① It is plain common sense from those authoritative research that…
② Based on their findings, the experts claim that…

statistics 统计数据法

reduce
dramatically

① According to a recent survey, thousands of people has found 80% of respondents claimed that…

example 举例子 (文章字不够举例子来凑) (2个)

deal with = cope with = dispose of (解决)
efficiently(有效地) / effective(有效率的)
concentrate on = pay attention to = absorb in = dedicate to = indulge in = focus on

① Taking a look around, we can find the following examples in our daily lives.
② A good case in point is that…
③ As an illustration, we may take …as an example.
④ A typical example of this is that…
A typical example of this is that in most families, there is only one child who is loved by not only patents, but also grandparents.

personal experience 个人经历
Logic & Reasoning 逻辑推理

分析原因段 (一般都是第二段)
​ ① 三个原因 + 一个例子
​ ② 两个原因 + 两个例子

解决问题段
​ deal with = cope with = dispose of (解决)

​ ① 国家政府角度谈 (权威)
​ ② 全社会人民角度谈 (广泛)
​ ③ 就我个人而言 (个人)

known facts 常识法

consider/considerate(adj)
consideration(共同的名词词性)[考虑/]
take sth. into consideration

① It is truism that…
② It comes to light that…

analogy 类比(目标A 但是去说B最后与A关联)

tend(照顾,倾向于) extend(延伸,口) pretend(假装) intend(打算) = mean to do

第二部分:四级写作的出题方式

(一) 提纲式作文

… on the topic Will E-books replaced traditional books? You should write at 150 words following the outline given below in Chinese:
① 随着信息技术的发展, 电子图书越来越多
② 有人认为电子图书会取代传统图书
③ 我的看法

(二) 图表作文/漫画式作文

(三) 应用文

… to write a letter to a company declining a job offer.
① 对公司提供职位标识感激
② 有人认为电子图书会取代传统图书
③ 我的看法

(四) 记叙文

… to write a composition on the topic “The most unforgettable person I ever know.


第三部分:四级的三段论

(一) 现象描述型

第一段:描述现象,引出话题
第二段:阐述正反观点
第三段:观点结论

(二) 问题解决型

第一段:问题描述
第二段:解决问题的原因
第三段:解决问题的措施

(三) 对比观点型

第一段:正方观点 + 理由
第二段:反方观点 + 理由
第三段:观点结论

(四) 应用文 (每段的第一个句子要成为主题句出现 三个主题句)

第一段:写信目的
第二段:写信内容
第三段:结束语

(五) 引语类

第一段:引语含义
第二段:观点阐述
第三段:观点结论

(六) 漫画/图表作文

第一段:描述漫画/图表内容
第二段:分析原因/观点阐述 (可单独 可融合)
第三段:展望未来/解决问题

(七) 记叙文

第一段:描述概况
第二段:描述事件经过细节
第三段:得出结论


第四部分:四级写作评分实例(阅读范文)

… on the topic Should one expect a reward when doing a good deed ?
① 有人做好事期望得到回报
② 有人认为应该像雷锋那样做好事不图回报
③ 我的观点
每当写完一个句子 就问问自己后面能写原因吗**(原因状语从句)**

​ Should one expect a reward when doing a good deed
Different people hold different view about whether or not one should expect a reward when doing a good deed. Some argue(替代think) that people should be given a certain reward for their good deed, because they believe(替代think) the saying “No pain, no gains”. In addition(替代and), they maintain(替代think) that in some sense rewards can stimulate(代替encourage) people’s enthusiasm to do good deeds.

① argue可以替代think的
单词:assume,deem,reckon,maintain
短语:

​ (1) hold the opinion/ belief/ point that…
​ (2) harbor the idea that…
​ (3) take the attitude that…
​ (4) be firmly/ fully convinced that…

句子:

​ (1) It is widely shared that…
​ (2) It is universally acknowledged

argue的扩展
I think
= to my knowledge
= from my personal perspective
= from my point of view
= from my angle

所有的介词或介词短语后面都要加 名词/名词短语。只有连词后面才可以加句子

**② because of **+ 短语 (because of your help)
on account of、due to、thanks to、by virtue of、in light of、
be responsible for

③ because + 句子 (because you help me)
in that、for the reason that、on the ground that、seeing that、considering that

④ 动词短语

  • (1) 前 “导致”
    • contribute to
    • lead to
    • result in

例句:Eating too much fat can ___ heart disease.

  • (2) 前 “归因于”
    • attribute to
    • result from
    • credit to (credit hour学时 credit card信用卡)

例句:The accident resulted from the driver’s carelessness.
例句:The driver’s carelessness **resulted in **the accident.

  • (3) 混合因果 (誰前誰后都可以)
    • be bound up with
      (be bound to do sth The cars are bound to be disused[可+原因状语从句].)
    • be associated with
      (association 协会/团队)

例句:Cancer is bound up with smoking.
例句:Smoking is bound up with cancer.

However, others have different viewpoint. They regard LeiFeng as a model, who(定语从句)) always helps others without ever expecting any reward. They assume that doing a good deed should be based on people’s individual consciousness of responsibility, and hence(结果从句) little(不…) significance should be attached to monetary reward. In addition, one can enjoy the happiness and peace through what(宾语从句) he had done, they think, which(定语从句) is the best return.

Personally, I prefer the latter(former) opinion. It is imperative for us to(对…来说做…是) conduct good deeds without expecting any reward. For one thing, the authorities should make laws and regulations to encourage people to do(法律法规) good deeds. For another, people’s awareness should be cultivated that conducing good deeds is extremely(very) meaningful to our lives. It should be universally acknowledged that we do good deeds for enjoyment, self-fulfillment and spiritual enhancement, not for the rewards.

第二部分:语言丰富

一、长句|短句错落有致 (3-4)

二、主动|被动适当穿插 (2-3)

三、比较结构巧妙使用 (1-2)

四、强调倒装恰当运用 (1-1)

五、句和句间衔接紧密


一、短句
  • 类型1:主语 + 谓语 (I seen)
  • 类型2:主语 + 谓语 + 宾语 (主体+动词+对象)
  • 类型3:主语 + 系动词 + 表语 (主体+系动词(be_感官)(动词分支)+对象)
  • 类型4:主语 + 谓语 + 双宾语 (主体+动词+对象+对象)(都可以改被动就是双宾否则就是宾补)
  • 类型5:主语 + 谓语 + 宾语 + 宾补 (主体+动词+对象+对宾语的补充说明)

只有名词、形容词、副词才可以变成句子(名词性从句、形容词性从句[定语从句]、副词性从句[状语从句])

长句

(1) 定语从句

① This is the present that he gave me for my birthday. (什么样的present)
The person who broke the window must pay for it.
Mr.Lee(whom) you want to see has come.
The girl whose mother is ill is staying at home today.
⑤ A dictionary is a book which gives the meaning of words.

任何一个主语后面都可以加一个定语从句主语进行修饰
任何一个宾语后面都可以加一个定语从句宾语/双宾进行修饰
每当造完一个短句子都要时刻提醒自己能不能加定语从句 [用who whom 用which that都可以用]

(2) 状语从句 (与含义相关 两句话粘一起) (本质是连词[连接句子])

时间地点是一对儿
原因结果为一家;
条件目的有虚拟;(一般不用)
比较喜欢让步方式
人,物 + 定 (想一想能不能加!)
句子 + 原因结果时间

1) 时间状语从句 (造完句问自己能产生时间吗)

定语从句是内战 状语从句是侵略战争(拉一个句子来)(寻找句子之间的逻辑关系)
When I arrived home, I had a little rest.
He sang as he danced.
You will grow wiser as you grow older.

2) 条件状语从句:(真实条件句和虚拟条件句)

You will fail unless you study hard.
You will not fail if you study hard.
You may go there, as long as you keep quite.
I shall give you the book on condition that you return it tomorrow.
I will go provided that you go too.

3) 让步状语从句

Though/Although it is raining, they are still running outside.
Child though he was, he knew what was the right thing to do.
Tired as he is, he goes on with his work.
Despite/In spite of wanting to see him again, she refused to reply to his letter.

4) 原因状语从句(+强调句) [找主谓宾 关联连接] (任何一句话都可能变成)

It is because she is too inexperienced that she does know how to deal with the situation
As/Since you are not very well[主谓宾], you had better have a rest.[主谓宾]
= It is Since you are not very well that you had better have a rest

It must have rained last night[主谓宾], for the ground is wet now.[主系表]
= It is because/for the ground is wet now that It must have rained last night

5) 目的状语从句 (怎么怎么滴… 为了… 以防…)

You must speak louder so that/in order that you can be heard by all.
He wrote the name down for fear that he should forget it.
Better take more clothes in case the weather be cold.

注意:lest, in case, for fear that所引导的状语从句中,动词可以用虚拟语气,形式为 should + 动词原形 (条件目的有虚拟)

People must 关注 the P, lest it should be more and more 严重

6) 结果状语从句
引导结果状语从句的连词有:so that, so…that, such…that

He is so young that he can not go to school.
He is such a young boy that he can not go to school.

补充常用句型

① so + 形容词/副词 + that从句
② so + 形容词 + a/an + 单数可数名词 + that从句
③ such + a/an + 形容词 + 单数可数名词 + that从句
④ such + 形容词 + 复数可数名词/不可数名词 + that从句

7) 比较状语从句
引导比较状语从句的连词有:as…as, than, not so…as, the more…the more等

He speaks English as fluently as his brother(does).
I can’t jump so/as high as he(does).

8) 地点状语从句
引导地点状语从句的连词有:where, wherever以及 where构成的复合词等

① She found her calculator where she lost it.
② Wherever I am, I will miss you.
③ I happened to see your dictionary lie where I used it last time.
④ I happened to see your dictionary lie in the lab where I used it last time.

9) 方式状语从句

① Just do as you are told.
② The workers went on working as if nothing had happened.

(3) 名词性从句

1) 主语从句

① That she had won the first place(主谓宾) made us very happy(主谓宾宾).
五之一完整的主谓宾 (原因,结果,目的,条件) 五之一完整的主谓宾
【五之一+连词+五之一】
主语比较长的时候避免头重脚轻 可以用It做形式主语
= It made us very happy that she had won the first place.

② Which team will win the match
= It is still unknown which team will win the match is still unknown.
五之一完整的主谓宾 但是这是主语从句 所以主语就变成了一个句子
people protect the environment(主谓宾做主语) is important要用连词连接一起
= It is important that people protect the environment.(It可做形式主语但不是必须)

补充:用it作形式主语的that-从句
1. It + be + 形容词 + that从句

① It is necessary that… 有必要…
② It is important that… 重要的是…
③ It is obvious that… 很明显

2. It + be + -ed分词 + that-从句

① It is believed that… 人们相信…
② It is known to all that… 纵所周知
③ It has been decided that… 已决定

3. It + be + 名词 + that-从句

① It is common knowledge that… …是常识
② It is a surprise that… 令人惊奇的是…
③ It is a fact that… 事实是

每当写完一个人能不能在人后面加定语从句
每当写完一个物能不能在物后面加定语从句
每当写完一个句子想一想在后面能不能加原因/结果/时间状语从句
写主语的时候想一想能不能把一个句子当主语

2) 宾语从句
3) 表语从句
4) 同位语从句

语言丰富方式二:主动被动适当穿插

1.被动语态的构成

**① 一般现在时**:am/is/are + done
**② 一般过去时**:was/were + done
**③ 一般将来时**:will/shall/be going to be + done
④ 过去将来时:would/should/be going to be + done
⑤ 现在进行时:am/is/are being + done
⑥ 过去进行时:was/were being + done
⑦ 过去完成时:had been + done

2.被动语态的用法(至少3个)
1) 不知道或没有必要说明动作的执行者是誰 (句子没主语)

① Some new computers were stolen last night.
② This book was published in 1981.

2) 强调动作的承受者

① Eight hours per day for sleep must be guaranteed.

3) 主动语态变被动语态的方法

① 把主动语态的宾语变为被动语态的主语
② 把谓语变成被动结构 (be+过去分词) [被动形式]
③ 把主动语态中的主语放在介词by之后宾语, 将主格改为宾格

Ⅰ. All the people laughed at him.[一般过去时]
He was laughed at by all the people.

Ⅱ. They make

4) 含有情态动词的被动语态:”情态动词 + be + done”

Ⅰ. We can repair this watch in two days.
This watch can be repaired in two days.
Ⅱ. We must finish this work soon.
This work must be done soon.

5) 主动语态变被动语态需要注意的几个问题

① 时态保持一致
Ⅰ. I have repaired my computer. [现在完成时]
My computer has been repaired.

② 被动语态不能丢掉动词短语的介词或副词
Ⅰ. His best friend often looks after him.
He is often looked after by his best friend.

③ 若双宾语(一个人一个物),将其中一个宾语变为被动句的主语,另一个宾语不变
make/buy/get用for; give/send/lend/take用to
Ⅰ. Vivian gave me a book
I was given a book by Vivian.
A book was given to me by Vivian.
双宾: make/buy/get用for; give/send/lend/take用to
一定是一个人一个物
如果先都说人 无论主动被动都没有介词
I’ll give you the child

如果先都说物 无论主动被动都有介词
I am writing to your child

④ 若复合宾语,将主动句的宾语变成被动句的主语,宾补不变
Ⅰ. They call him Louis.
He is called Louis.

⑤ “动词+宾语+宾语补足语”, 将宾语变为被动结构中的主语, 其余不动
Ⅰ. Someone caught the boy smoking a cigarette.
The boy was caught smoking a cigarette.

⑥ It is said that+从句及类似句型 【据说… 人们…】

​ “It + be + 过去分词 + that从句” “主语 + be + 过去分词 + to do sth”

被动句型(不会改被动就直接套用)
① It is said that... 据说
② It is reported that... 据报道
③ It is believed that... 大家相信
④ It is hoped that... 大家希望
⑤ It is well known that... 众所周知
⑥ It is thought that... 大家认为
⑦ It is suggested that... 据建议

语言丰富方式三:比较结构巧妙使用

1.同级比较
① as…as(出现形容词副词); the same as; the identical with; 与…一样

She has written as many essays as her brother.

② no more… than 或 not…any more than; 和…一样不 (A是不… B也不…)

the more…the more句型【越来越… 越… 越…】

The heart is no more intelligent than the stomach, for they are both controlled by the brain.

2.表最高程度
① no + 名词 + more…than; 没有什么比…更…; 莫过于

No tool is more powerful for understanding the natural world than the scientific method.拉长 添加状语 定语

② more…than + anyone(和人比)/anything(和物比) else; 比其他任何…都…、没有比…更…

He did more work this morning than anyone else.

3. 表示倍数 【as、of、than】

as…只能跟adj/adv of…只能跟n than…只能跟比较级

① 倍数 + as + 形容词或副词 + as

An ordinary subway train, approaching the station, can be twice as loud as the loudest jet.

② 倍数 + 名词 + of

The bridge is three times the length of that one.

③ 倍数 + 比较级 + than

This hole is three times deeper than that one.

4. more than
① more than + 动词或名词:”不仅仅是, 不只是”

They may teach very well, and more than earn their salaries, but most of them make little…

② more than + 形容词:非常 (代替not only、very、over)

The USA remains more than capable of holding down.

语言丰富方式四:强调倒装恰当使用

(一) 强调句:It is/was + 被强调的部分 + that + 句子的其他部分

(谓语以外的任何成分都可以放在被强调部分当中进行强调)

改写句子①

Wester health-care system are spending huge sums of money on the surgical treatment of the disease.( 可以改成三个强调句)
① It is Wester health-care system that are spending huge sums of money on the surgical treatment of the disease.
② It is huge sums of money that Western health-care system are spending on the surgical treatment of the disease.
③ It is **on the surgical treatment of the disease ** that Western health-care system are spending huge sums of money.

改写句子②

Ann Peters’ husband rushed her to a nearby hospital last night.
① It was Ann Peters’ husband that(who) rushed her to nearby hospital last night.
② It was her that(who) Ann Peters’ husband rushed her to nearby hospital last night.
③ It was to a nearby hospital that Ann Peters’ husband rushed her last night.
④ It was last night that Ann Peters’ husband rushed her to a nearby hospital.
提示1:
1.叙述的是现在或者将来:“It is … that … ”
2.叙述的是过去:”It was … that …”

提示2:

强调动词的时候 没有强调句子谓语的强调句,但又其通常的强调手段:do / does / did + 动词原形

I do believe that he is an honest man.
They did go to see you yesterday, but failed to meet you.

(二) 倒装:(主谓 → 谓主) ( 3个被动 定语 原因状语 强调句 倒装[only时间状语 so…that])

① 指将谓语的一部分(如助动词或情态)至主语之前
② 如果句子的谓语没有助动词或情态动词,需加助动词do, does或did, 将其置于主语之前

全部倒装

要把所有的谓语都拿到主语前面 和其他成分无关

部分倒装

要把谓语的一部分拿到主语前面
助动词+动词 情态动词+动词

1. only句首

Only in this way can you learn(介词短语) English well.
Only after being asked three times(非谓语动词) did he come to the meeting.
Only when he is seriously ill(从句) does he ever stay in bed.
Only when 大学生… do they 意识到…

2. 句首为否定意义的词语
如:no, never, seldom, little, hardly, at no time, in no way, not until…等

① Never have I seen such a performance.
② Nowhere will you find the answer to this question.
③ Not until the child fell asleep did the mother leave the room.

3. so…that 句型中的so位于句首时,需倒装

he was so frightened that he did not dare to move an inch.
=So frightened was he that he did not dare to move an inch.

4. 在虚拟语气中, 可将if省略, 采取部分倒装

Were I you, I would try it again

3个被动 定语 原因状语 强调句 倒装 写作必须拥有的

八种万能功能段 (三八大盖)

现象/问题描述功能段

In recent years, … is becoming increasingly popular/prevalent at an amazing rate.
近年来, …越来越流行(现象)
Along with the advance of the society more and more problems are brought to our attention, one of which is that…
随着社会的不断发展,出现了越来越多的问题引起我们的注意 ,其中之一便是…

原因分析功能段 (总原因的阐述)

A number of factors might contribute to/account for the phenomenon/problem.
许多因素可能是造成这种现象/问题的原因。
② The reasons for … mainly lie in the following two aspects.
原因…主要表现在以下两个方面。
(分原因阐述=>原因数量决定)
One of the most common factor is that…
② Another contributing cause is…
③ Perhaps the primary reason is that…
④ But the fundamental element is that…

观点阐述功能段

观点主题句
People have (take,adopt,assume) different attitudes towards.
② People have different opinions on this problem.

观点
According to a survey, some people are in favor of the idea of doing… They point out the fact that(+支持…的第一个原因) they also argue that… (+支持…的另一个原因)

观点结论功能段

From what has been discussed above, we may safely draw the conclusion that…
根据以上讨论,我们可以得出下面的结论

解决问题功能段

措施主题句
① ___ has been playing an increasingly important role in our day-to-day life, which has brought us a lot of benefits but has created some serious problems as well.
…在日常生活中发挥着越来越重要的作用, 它给我们带来了很多好处

分措施
First and foremost, government should establish some regulations to…
What’s more, people should get ready to…
After all, it is necessary to advocate the whole society to…

解决问题总结句
① In summary, if we continue to ignore the above-mentioned issue, more problems will crop up.
总之,如果我们继续忽略上述问题,会有更多问题出现
② From my point of view, college students should … when it comes to … What’s more, we should always bear in mind that…
我认为, … 大学生应该自己…此外, 我们应牢记…

未来展望功能段

① It can directly promote the rapid progress of … , and eventually brings …
它可以直接促进…还可也用来…

漫画/图表描述功能段

① It can be easily seen from the picture(chart/line/graph) that… The picture is intended to inform us of the bad effects of college student’s…
从图片中很容易可以看到, …这幅画是在告诉我们 … 有着不好的影响
According to the figure/number/statistics in the chart/ bar graph/line, it can be seen that…

引语解释功能段

There is such a saying … This saying not only emphasizes the importance of… but also indicates that…
有这样一句名言:….这句话不仅强调了…的重要性,而且指出…

附加:举例子

① There may be no better example than the following one.

阅读全文

Linux,Redis,Jedis

2023/11/15

Linux基础入门

Linux操作系统

  • 介绍Linux与CentOS
  • 讲解Linux基础
  • Linux文本工具与命令
  • yum应用安装与卸载
  • CentOS的权限与系统安全
  • 部署OA项目至Linux服务器

主流操作系统

不同领域的主流操作系统,主要分为以下这么几类: 桌面操作系统、服务器操作系统、移动设备操作系统、嵌入式操作系统。接下来,这几个领域中,代表性的操作系统是那些?

1). 桌面操作系统

操作系统 特点
Windows 用户数量最多
MacOS 操作体验好,办公人士首选
Linux 用户数量少(桌面操作系统,Linux使用较少)

2). 服务器操作系统

操作系统 特点
Unix 安全、稳定、付费
Linux 安全、稳定、免费、占有率高
Windows Server 付费、占有率低

3). 移动设备操作系统

操作系统 特点
Android 基于 Linux 、开源,主要用于智能手机、平板电脑和智能电视
IOS 苹果公司开发、不开源,用于苹果公司的产品,例如:iPhone、 iPad

4). 嵌入式操作系统

操作系统 特点
Linux 机顶盒、路由器、交换机

2.2 Linux系统版本

Linux系统的版本分为两种,分别是: 内核版 和 发行版。

1). 内核版

  • 由Linus Torvalds及其团队开发、维护

  • 免费、开源

  • 负责控制硬件

2). 发行版

  • 基于Linux内核版进行扩展

  • 由各个Linux厂商开发、维护

  • 有收费版本和免费版本

我们使用Linux操作系统,实际上选择的是Linux的发行版本。在linux系统中,有各种各样的发行版本,具体如下:

发行版本 Logo 特点
Ubuntu image-20210809001838861 以桌面应用为主
RedHat image-20210809001731378 应用最广泛、收费
CentOS image-20210809001741238 RedHat的社区版、免费
openSUSE image-20210809001750999 对个人完全免费、图形界面华丽
Fedora image-20210809001800676 功能完备、快速更新、免费
红旗Linux image-20210809001814942 北京中科红旗软件技术有限公司开发

除了上述罗列出来的发行版,还有很多Linux发行版,这里,我们就不再一一列举了。

什么是操作系统

  • 操作系统(Operating System)是应用程序运行的基础支撑环境
  • 操作系统作用是管理和控制计算机系统的硬件与软件资源
  • Intel x86架构上常见的操作系统:Windows、Linux、Unix…

Linux操作系统

  • Linux是开源的基于Inter x86架构的类Unix多用户操作系统
  • 支持多任务、多用户、多CPU
  • 高效而灵活
  • 兼容任意x86架构计算机
  • 强大易用的系统命令
  • 完整的应用软件生态

Linux发行版本

  • Linux系统内核(kernel)提供了Linux操作系统的核心功能
  • 不同开发商在内核基础上扩展封装,形成了不同发行版本
  • 常见发行版:Red Hat Linux、CentOS、Ubuntu、SUSE…

Linux发行版选择建议

  • 桌面系统:Ubuntu
  • 服务器操作系统:**CentOS(免费)**、Red Hat Linux(收费)
  • 特定需求:Debian(稳定性)、Fedoras(新特性)、麒麟Linux(国产)

CentOS - 社区企业操作系统

  • 基于Red Hat Enterprice Linux的开源企业级Linux发行版本
  • 各版本CentOS都会获得十年的支持,与RHEL保持同步更新
  • CentOS采用社区支持,同步修正了RHEL许多BUG

CentOS版本选择

  • CentOS 5/6:历史淘汰版本
  • CentOS 7:主流版本,成熟稳定,大多数服务器的首先版本
  • CentOS 8:全新版本,全新内核,存在漏洞隐患

安装CentOS 7.7

vmware.com/cn.html

VMware-workstation-full-14.1.2-8497320.exe
[链接: https://pan.baidu.com/s/1EbuhZ4D4kh_NcRJqTA-vhQ
密码:oq37]

mirrors.aliyun.com/centos/7.7.1908/isos/x86_64/ 下载 Everything-1908.iso
[centos-vault-centos-7.7.1908-isos-x86_64安装包下载_开源镜像站-阿里云 (aliyun.com)]

VM典型 镜像Iso 存储地址放在最快的硬盘 将虚拟磁盘存储为单个文件 自定义(内存2GB 处理器2核 虚拟化 Intel VT-x)

Linux目录结构

根目录/ 下各个目录的作用及含义说明:

编号 目录 含义
1 /bin 存放二进制可执行文件
2 /boot 存放系统引导时使用的各种文件
3 /dev 存放设备文件
4 /etc 存放系统配置文件
5 /home 存放系统用户的文件
6 /lib 存放程序运行所需的共享库和内核模块
7 /opt 额外安装的可选应用程序包所放置的位置
8 /root 超级用户目录
9 /sbin 存放二进制可执行文件,只有root用户才能访问
10 /tmp 存放临时文件
11 /usr 存放系统应用程序
12 /var 存放运行时需要改变数据的文件,例如日志文件

Linux命令格式

命令 [参数选项] [文件或路径]
Linux文件操作核心命令
命令 用途
cd 切换目录
pwd 查看当前目录
ls、ll(详细列表) 显示目录内容
mkdir 创建目录
cp 复制文件与目录
mv 移动或重命名文件
rm 删除文件或目录
find 查找目录或文件

Xshell连接虚拟机详细教程-CSDN博客

按下Tab可以自动提示
输入su 密码root 进入root最高权限

cd ..                 返回上级目录
cd ./local             ./代表当前目录
cd local            到当前目录    

mkdir ./imooc
mkdir -p ./imooc/sample/demo       -p是连续创建多级目录

mkdir -p ./imooc1/sample1
mkdir -p -v./imooc1/sample1     -v是显示执行过程
mkdir -p -v./imooc1/sample1/demo1
=
mkdir -pv ./imooc1/sample1
    
cp Xftp-7.0.014lp.exe ./imooc/sample/demo 文件复制到指定目录
cp -r imooc/sample/demo imooc1/sample1/demo1 复制整个文件夹

cd imooc/sample/demo
mv Xftp-7.0.014lp.exe xftp.exe 重命名
ls

mv xftp.exe /imooc1/sample1/demo1 移动到其他目录
mv demo imooc1/sample1/demo1 移动文件夹到其他目录

cd game/share
rm -r music  y y y    依次删除文件夹中的文件
rm -f music 强制直接删除, 不经过询问
rm -rf music 强制迭代删除            【不要轻易使用】
【ex】 rm -rf / imooc/sample/demo 中间不小心加了空格 就把/后面的整个都删除了

find / -name *.exe 按指定的格式去搜索文件
cd ..
[root@imooc home] find / -name *

vim文本编辑器

远程在线文本编辑器
  • vi是linux重要的文字编辑工具,vim是增强版
  • vim用于在远程环境下用命令形式对文本进行在线编辑
  • 用法格式:vim [选项] [文件]

vim三种模式

  • 普通模式:默认模式,文本只读,不可编辑
  • 编辑模式:编辑文本模式,普通模式按i键进入,ESC键退出
  • 命令模式:执行保存、搜索、退出等操作

vim重要快捷键

i进入编辑模式

命令 用途
delete或x 删除单个字符
dd 删除整行
/str 全文查找str字符串,n下一个,N前一个
:% s/old/new/g 替换文件内所有old字符串为new
u 撤销最近一次操作
:wq或者**:wq!** 退出并保存,只读文件要格外加!
:q! 强制退出放弃保存
vim server.xml         修改文件
按i进入编辑模式
Home跳转行首
End跳转行尾
Esc退出编辑模式

命令只允许在普通模式下运行
/8080                     查找端口(高亮显示)
查找到后按n是查找下一个 按N是查找上一个
u  撤销最近一次操作
:% s/8080/8900/g        进行端口的全局替换
:% s/"80"/8900/g         想要把80改成8900 且不影响其他数值[把引号也加入其中]

Linux文本工具

常用文本工具
命令 用途
echo 屏幕打印与文本输出 后面要加引号”hello”
cat 合并文件或查看文件内容
tail 显示文件内容尾部
grep 文本过滤工具
echo "hello" > hello.txt 将左侧命令的结果重写到右侧文件
vim hello.txt
:q        退出

echo "hello" >> hello.txt 内容的追加

cat hello.txt 直接打印到控制台
cat -n hello.txt  -n是在每一行前加行号
cat -nE hello.txt  结尾的时候显示结束符$

echo "hello" > hello.txt
cat hello.txt  => hello
echo "my name is lili" > myname.txt
cat hello.txt myname.txt >> full.txt    合并文件内容为新的文件    
vim full.txt
echo 和 cat 在实际开发中 打印日志 最后用cat拼接日志

cat > test.txt << EOF   在当前的输入环境中产生输入流 输入的全输入到test.txt 输入流结束字符串是EOF
> Welcome to imooc.com
> I like linux
> bye!
> EOF

tail处理日志很方便 bug日志
tail full.txt => hello       my name is lili
tail -n 1 full.txt => my name is lili   "1"代表显示文本最后一行
tail -f full.txt      对文件进行监听 一旦产生变化就输出控制台【适合用在服务器】
ctrl + c 退出

#cat test.txt
=> welcome to imooc.com
=> I like linux
=> bye!
#grep l test.txt
we"l"come to imooc.com
I "l"ike "l"inux

#grep l test.txt > out.txt            符合条件的可以输出到out.txt文件中
#grep -v l test.txt                    -v 挑选出不包含l的test.txt文件
#grep i...c test.txt                "."代表匹配任意字符

#ll | grep .txt                     通道特殊使用方法:筛选出后缀是.txt的 
前面ll命令输出的结果会作为后面gerp命令的依据   通过|进行分割 

#ll | grep -E "log[0-9]{1,5}.txt"    通道特殊使用方法:扩展正则表达式
-rw-r--r--. 1 root root 3 12月 23 13:10 log1.txt
-rw-r--r--. 1 root root 3 12月 23 13:10 log2.txt
-rw-r--r--. 1 root root 3 12月 23 13:10 log3.txt

文件打包[文件组织]与压缩[磁盘节省]

Linux压缩程序-gzip
  • gzip是Linux系统的文件压缩程序
  • gzip压缩包文件扩展名 .gz
  • 大流量的网站默认都在使用 gzip 进行数据压缩传输
tar打包与压缩
  • tar是linux系统将多个文件打包和压缩的工具
  • tar本质的打包软件,扩展名是 **.tar **
  • tar可结合gzip或其他压缩工具实现打包压缩,扩展名 .tar.gz
  • 压缩命令:tar zcvf tomcat.tar.gz game/src
  • 解压缩命令:tar zxvf tomcat.tar.gz -C game/src

tar常用可选项

选项 用途
z 通过gzip压缩或解压
c 创建新的tar.gz文件
v 显示执行过程
f 指定压缩文件名称
x 解压缩tar.gz文件
-C 指定解压缩目录

安装与卸载应用程序

为CentOS安装应用程序
  • 在CentOS中安装第三方应用程序包含两种方式
    • rpm: Red Hat 软件包管理器, 相当于应用程序安装文件的执行者
    • 编译安装:用户自己从网站下载程序源码进行编译安装

yum与rpm的关系

  • rpm安装过程中, 需要用户自己解决依赖问题
  • yum通过引入软件仓库,联网下载rpm包及依赖,并依次自动安装
  • yum是rpm的前端程序,其目的就是简化rpm的安装过程
yum常用命令
  • yum search 应用名 #在仓库中查询是否存在指定应用
  • yum instal -y 应用名 #全自动下载安装应用及依赖
  • yum info 应用名 #查看应用详细信息
  • yum list installed 应用名 #查看已安装的应用程序
  • rpm -ql 应用名 #查看安装后输出的文件清单
  • yum remove -y 应用名 #全自动卸载指定应用
① 利用yum安装tree
yum search tree
yum install tree.x86_64 安装tree
yum install -y tree.x86_64 遇到所有的默认y进行安装
tree -d 或 tree                查看文件树型结构
which tree                     查看刚刚tree安装的目录
rpm -ql tree.x86_64         查看刚刚tree安装了哪些文件
yum list installed          查看已经安装的应用
yum list installed *tree*   查看已安装的应用其中有tree的
    
yum remove tree.x86_64  卸载程序
② 利用编译方式安装应用程序
  • 如yum仓库未提供rpm,往往需要采用编译安装方式
  • 编译安装是指从应用网站下载源码后,对源码进行编译后使用
  • 编译命令:**make #**使用对应编译器对源码编译生成可执行文件
yum与编译安装的比较
yum安装 编译安装
兼容性 差,每种发行版都要准备 好,全平台适用
复杂度 简单 复杂
安装速度
来源 应用仓库 官网下载
应用场景 日常系统软件 开源软件,最新版本

编译安装Redis[非关系型数据库]

# make
# yum install -y gcc
# cd ..
# clear
# rm -rf ./redis-4.0.14
# tar zxvf redis-4.0.14.tar,gz
# cd redis-4.0.14/
# clear
# make

[root@imooc redis-4.0.14]# ./src/redis-server redis.conf

Linux系统管理命令

使用 ifconfig 查看网卡ip

netstat 查看网络端口号

  • netstat -tulpn 或者 netstat -ano

    • netstat 常用选项

      选项 用途
      t 显示tcp传输协议的连接状况
      u 显示udp传输协议的连接状况
      l 显示处于监听状态的网络连接
      p 显示应用PID和程序名称
      n 显示ip地址
      a 显示所有连接
      o 显示计时器

查看进程 & 杀掉进程

  • ps -ef
    # ps -ef | grep vim ‘|’代表通道
  • **kill -9 PID ** 专门按照pid杀死指定进程(强制删除)
#ps -ef | grep redis
#netstart -tulpn | grep 6379

应用服务化

  • 应用服务化是指让应用程序以服务方式在系统后台运行
  • Linux系统对服务化应用进行统一管理
  • 服务管理命令:systemctl
指令 用途
start 启动服务
stop 停止服务
restart 重启服务
enable 设置开机启动
disable 禁止开机启动
status 查看服务状态
daemon-reload 重载服务配置文件
list-unit-files 列出所有服务
启动redis
./src/redis-server redis.conf  进入到redis安装目录下执行
ctrl + z 
#ps -ef | grep redis

#find / -name *.pid

cd.. cd.. cd.. 
cd usr/lib/systemd/system 
#pwd
=>/usr/lib/systemd/system 
#vim redis.service 按i进入编写模式
=>
<==========================================================================>
[Unit]
Description=Redis
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking                    #描述服务类型:后台运行
PIDFile=/run/redis_6379.pid     #指向刚刚继承编号的pid
ExecStart=/usr/local/redis-4.0.14/src/redis-server /usr/local/redis-4.0.14/redis.conf                                 #服务启动时使用什么命令 调用redis-server
ExecStop=/bin/kill -s QUIT $MAINPID 
                                #对指定的命令关闭 $后自动带入pidfile  -s quit是按正常流程关闭
PrivateTmp=true
            
[Install]
WantedBy=multi-user.target         #将radis分配到multi-user.target服务组上[随系统自动启动]
<==========================================================================>
[root@imooc system]#systemctl daemon-reload   对所有redis进行重载

[root@imooc system]#ps -ef | grep redis  
[root@imooc system]#kill -s QUIT #杀死所有redis进程测试能否实现自启动
[root@imooc system]#systemctl start redis
[root@imooc system]#systemctl status redis

https://www.cnblogs.com/niway/p/15346572.html

[root@imooc system]#systemctl stop redis  #停止服务
[root@imooc system]#systemctl enable redis #随着系统启动
[root@imooc system]#systemctl list-unit-files #查看系统中每一个服务命令
[root@imooc system]#systemctl list-unit-files | grep enabled #查看自启动的命令

[root@imooc system]#shutdown -r now   #断开连接

Linux用户与权限

用户
  • Linux是多用户多任务系统,包含两个概念:用户用户组
  • 用户与账户是同一概念,用于登录系统与区分资源权限
  • 用户让系统变的更安全,同时也保护了用户的个人数字资产
用户组
  • 用户组就是将用户分组,隶属用户自动拥有组权限
  • 一个用户可隶属于多个组,用户可任意切换当前组
  • 用户组的出现让用户权限管理变更轻松
用户与用户组的常用命令
命令 用途
useradd 创建新用户
passwd 修改密码
usermod 修改用户信息/分配组 (覆盖原组)
groupadd 创建新的用户组
chown 更改文件的属主或属组
chmod 更改文件的访问权限
newgrp 切换用户当前组

项目内部文件权限管理实践

3员工(2个程序员 1个测试员)
[root@imooc imooc]# adduser d1
[root@imooc imooc]# adduser d2
[root@imooc imooc]# adduser t1
[root@imooc imooc]# passwd d1 => shuangyu1
[root@imooc imooc]# passwd d2 => shuangyu2
[root@imooc imooc]# passwd t1 => shuangyu3
[root@imooc imooc]# groupadd developer
[root@imooc imooc]# groupadd testor
[root@imooc imooc]# usermod -g developer d1
[root@imooc imooc]# usermod -g developer d2
[root@imooc imooc]# usermod -g testor t1
在上方点击打开 并且复制3个Centos7 分别改用户名(d1 d2 t1)和密码(shuangyu1/2/3) 
[root@imooc imooc]# cd
[root@imooc ~]# cd /usr/local/share
[root@imooc share]# mkdir dev-document
[root@imooc share]# ll
drwxr-xr-x.  2 root root  28 11月 15 18:37 applications
drwxr-xr-x.  2 root root   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root   6 4月  11 2018 info
drwxr-xr-x. 21 root root 243 11月 15 18:25 man
【前方的drwxr-xr-x 含义见图】

[root@imooc share]# chown d1:developer dev-document  更改对应目录文件的属主(由超级管理员交給研发组) d1可以对developer拥有完整的操作权限 developer以外的用户有读取执行权 其他用户 只有执行权
drwxr-xr-x.  2 root root       28 11月 15 18:37 applications
drwxr-xr-x.  2 d1   developer   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root        6 4月  11 2018 info
drwxr-xr-x. 21 root root      243 11月 15 18:25 man

[root@imooc share]# chmod 750 dev-document/ 其他用户不允许任何权限
[root@imooc share]# ll
总用量 0
drwxr-xr-x.  2 root root       28 11月 15 18:37 applications
drwxr-x---.  2 d1   developer   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root        6 4月  11 2018 info
drwxr-xr-x. 21 root root      243 11月 15 18:25 man

返回到Centos 7 - t1
[t1@imooc ~]$ cd /usr/local/share/dev-document/
-bash: cd: /usr/local/share/dev-document/: 权限不够
返回到Centos 7 - d2
[d2@imooc ~]$ cd /usr/local/share
[d2@imooc share]$ mv dev-document/ doc
mv: 无法将"dev-document/" 移动至"doc": 权限不够


上方的chmod 750 的意思是 对应下方的表 第一个7是第一个rwx相加之和4+2+1
====================chmod命令====================
★ chmod 750:组用户可读写,其他用户不允许访问 ★        
★ chmod 777:所有用户拥有完整权限 ★
★ chmod 700:只有属主拥有完整权限 ★
====================++++++++====================
d1弄个文档整个公司的所有人都可以使用
[d1@imooc ~]$ cd /usr/local/share/dev-document/
[d1@imooc dev-document]$ vim code.md
[d1@imooc dev-document]$ ll
-rw-r--r--. 1 d1 developer 13 12月  3 20:59 code.md
第一个是d则是文件夹 -则是文件 属主可以对文件读写。组仅仅可以读取。其他人仅仅可以读取
[d1@imooc dev-document]$ chmod 770 code.md
[d1@imooc dev-document]$ ll
总用量 4
-rwxrwx---. 1 d1 developer 13 12月  3 20:59 code.md
d2即可读写
[d2@imooc dev-document]$ vim code.md

如何让d1同时拥有两个组
[root@imooc share]# usermod -G developer,testor d1
[root@imooc share]# groups d1
d1: developer testor

将当前组切换到新的用户组上
[d1@imooc ~]$ newgrp testor
[d1@imooc ~]$ groups
d1:testor developer

sudo获取超级管理员权限

  • sudo可以让普通用户拥有超级管理员的执行权限
  • 普通用户要经过超级管理员授权才能使用
  • 授权命令:visudo
在root端输入 visudo 并且输入100gg 快速定位到100行
[root@imooc ~]#visudo
## The COMMANDS section may have other options added to it.
##
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
d1        ALL=(ALL)        ALL     #按i进入编辑模式且增加此行
        ↑:ALL任意电脑可以连接    后ALL可以切换其他用户执行命令   后后ALL允许哪些命令
ESC退出模式后输入
[root@imooc imooc]# visudo -c
/etc/sudoers:解析正确


[d1@imooc ~]$ useradd d3    #目前还没有权限
useradd: Permission denied.
useradd:无法锁定 /etc/passwd,请稍后再试。
[d1@imooc ~]$ sudo useradd d3     #以d1的身份給d3一个超级管理员的权限
我们信任您已经从系统管理员那里了解了日常注意事项。
总结起来无外乎这三点:

    #1) 尊重别人的隐私。
    #2) 输入前要先考虑(后果和风险)。
    #3) 权力越大,责任越大。

[sudo] d1 的密码:=》 shuangyu1
[d1@imooc ~]$ sudo passwd d3 
更改用户 d3 的密码 =》 shuangyu33

回到主Centos 7
[root@imooc ~]#visudo
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
d1      ALL=(ALL)       NOPASSWD:ALL
NOPASSWD:ALL意味着做任何命令之前不用输入密码

[d1@imooc ~]$ sudo useradd d4
[d1@imooc ~]$                     #直接不需要输入密码了

CentOS7防火墙firewall

防火墙
  • 防火墙是借助硬件和软件对内外部网络环境的保护措施
  • CentOS 7基于firewall实现应用层防火墙,CentOS6基于iptables
  • firewall-cmd是firewall的核心命令
对外开放Tomcat
把apache-tomcat.tar.gz放入/usr/local中 
[root@imooc local]# tar zxvf apache-tomcat-9.0.34.tar.gz
[root@imooc local]# ll
[root@imooc local]# cd apache-tomcat-9.0.34/
[root@imooc apache-tomcat-9.0.34]# cd bin
[root@imooc bin]# ./startup.sh
[root@imooc bin]# ./startup.sh
Using CATALINA_BASE:   /usr/local/apache-tomcat-9.0.34
Using CATALINA_HOME:   /usr/local/apache-tomcat-9.0.34
Using CATALINA_TMPDIR: /usr/local/apache-tomcat-9.0.34/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/local/apache-tomcat-9.0.34/bin/bootstrap.jar:/usr/local/apache-tomcat-9.0.34/bin/tomcat-juli.jar
Tomcat started.
[root@imooc bin]# netstat -tulpn|grep 8080  #查看端口是否开启
tcp6    0    0 :::8080        :::*         LISTEN      4302/java  
在虚拟机内部去fox浏览器搜索 localhost:8080  就会看到可爱的绿色小猫咪
    
若想在计算机中访问端口 192.168.170.129:8080 则需要关闭虚拟机的防火墙
[root@imooc bin]# firewall-cmd --state   #查看防火墙状态
running
[root@imooc bin]# firewall-cmd --list-ports #查看防火墙放行的端口(空代表无任何放行端口)
[root@imooc bin]# firewall-cmd --zone=public --permanent --add-port=8080/tcp
#### zone防火墙定义的规则区域、产生永久变更、增加端口8080、tcp形式
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

若不需要开放8080端口则需要
[root@imooc bin]# firewall-cmd --zone=public --permanent --remove-port=8080/tcp
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

[root@imooc bin]# firewall-cmd --zone=public --permanent --add-port=8000-9000/tcp 
###放行区域端口
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

Bash Shell

  • Shell是一个用c语言编写的脚本解释器,是用户通过代码操作Linux的桥梁
  • Shell脚本描述要执行的任务,完成系列复杂操作,文件通常以**.sh**后缀
  • Shell脚本通过Shell解释器执行,按解释器分类分为多种类型

Linux Shell分类

Shell种类 Shell解释器
Bourne Shell /usr/bin/sh 或 /bin/sh
Boourne Again Shell /bin/bash(默认)
C Shell /usr/bin/csh
K Shell /usr/bin/ksh
Shell for Root /sbin/sh
一键发布Tomcat应用程序
编写shall脚本
[root@imooc local]# vim deploy_tomcat.sh
======================================================================
echo "准备下载Tomcat9"
wget https://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.34/bin/apache-tomcat-9.0.34.tar.gz
echo "正在解压缩Tomcat9"
tar zxf apache-tomcat-9.0.34.tar,gz
echo "防火墙开放8080端口"
firewall-cmd --zone=public --permanent --add-port=8080/tcp
firewall-cmd --reload
echo "启动Tomcat"
cd ./apache-tomcat-9.0.34/bin
./startup.sh
======================================================================
[root@imooc local]# ./deploy_tomcat.sh        开启文件

综合训练:Linux部署慕课网办公OA

部署架构:Chrome + Tomcat Web服务器 + MySQL服务器 【中间通过网络通信】
重新安装CentOS 7-DB【最小值安装】
[root@localhost ~]# yum install -y net-tools
CentOS 7-DB        ifconfig ==》 192.168.170.131    账户:centos-db 密码:panchunyao123
CentOS 7-WEB    ifconfig ==》 192.168.170.133    账户:centos-web 密码:panchunyao123
DB按照MySQL
WEB按照Tomcat

CentOS安装MySQL 8

查找有没有mysql安装包
[centos-db@localhost ~]$ yum search mysql-community
去官网找到Linux版的 右键复制链接地址
[root@localhost ~]# wget https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
bash: wget: command not found
卧槽 wegt不存在,那就按装wget
[root@localhost ~]# yum install -y wget
[root@localhost mysql]# ll
total 16
-rw-r--r--. 1 root root 14064 Oct 24 07:44 mysql80-community-release-el7-11.noarch.rpm

[root@localhost mysql]# yum localinstall -y mysql80-community-release-el7- 11.noarch.rpm     #自动安装mysql源
[root@localhost mysql]# yum search mysql-comm #此时查找一下就会出现大量mysql组件
[root@localhost mysql]# yum install -y mysql-community-server #安装mysql{在欧美很慢}
ctrl+c 停止安装
[root@localhost mysql]# cd /var/cache/yum/x86_64/7/mysql80-community/packages/
#yum下载的缓存路径 在网上下载好后Linux版的MySQL后 进入

[root@localhost mysql80-community]  /var/cache/yum/x86_64/7/mysql80-community
用命令更改文件夹权限:chmod 777 test01(需要赋予权限的文件夹)
之后将解压缩的MySQL拖进去

注意:如果直接下载 yum localinstall mysql80-community-release-el7- 11.noarch.rpm  的话会有很多依赖不会被自动下载,最保准的就是去网上下载Linux版本的MySQL再利用Xftp拖进去

[root@localhost package]# yum install -y mysql-community-server 【最快最省事 自动下载&安装依赖】
[root@localhost package]# systemctl start mysqld
[root@localhost package]# netstat -tulpn
tcp6    0    0    :::3306        :::*        LISTEM        21850/mysqld
[root@localhost package]# systemctl status mysqld     #查看myql启动状态
[root@localhost package]# systemctl enable mysqld     #设置开机自启动

初始化MySQL[CentOS 7-DB]

查看mysql日志寻找mysql密码
[root@localhost package]# vi /var/log/mysqld.log
root@localhost: Y,#)foTQ,7js
[root@localhost package]# mysql -uroot -p
修改密码!
mysql> alter user 'root'@'localhost' identified with mysql_native_password by 'Panchunyao123!'
mysql> use mysql
mysql> select host,user from user;
mysql> update user set host='%' where user='root'; #任意设备都可使用%连接到mysql服务器
host: %            user: root
mysql> flush privileges; #使修改的权限数据生效
mysql> exit

下一步是放行防火墙3306端口
[root@localhost package]# firewall-cmd --zone=public --permanent --add-port=3306/tcp
[root@localhost package]# firewall-cmd --reload

去电脑端的Navicat Premium 连接新数据库
MySQL-新建连接
连接名:centos-db
主机:192.168.170.131
端口:3306
用户名:root
密码:Panchunyao123!

新建数据库 imooc-oa  utf8mb4 执行sql文件 imooc-oa.sql

部署配置Web应用服务器

CentOS 7-DB        ifconfig ==》 192.168.170.131    账户:centos-db 密码:panchunyao123
CentOS 7-WEB    ifconfig ==》 192.168.170.133    账户:centos-web 密码:panchunyao123
[root@localhost ~]# yum search jdk  #查看仓库中包含哪些jdk
[root@localhost ~]# yum install -y java-1.8.0-openjdk    #安装jdk以及所有依赖
[root@localhost ~]# java -version
[root@localhost ~]# which java      #查看安装到哪个地方
/bin/java
安装tomcat
[root@localhost ~]# cd /usr
用命令更改文件夹权限:chmod 777 local
[root@localhost usr]# cd local
打开Xftp将apache-tomcat-9.0.34.tar.gz 传入到local内
[root@localhost local]# tar zxf apache-tomcat-9.0.34.tar.gz     #对压缩包进行解压
将素材资料里的imooc_oa.war拖入Xftp的local中 之后进行解压
[root@localhost local]# tar zxf imooc_oa.war 
需要将imooc_oa复制到tomcat9的webapps下才可生效
[root@localhost local]# mv imooc_oa ./apache-tomcat-9.0.34/webapps/
[root@localhost local]# cd apache-tomcat-9.0.34/webapps/
[root@localhost webapps]# vim ./imooc_oa/WEB-INF/classes/mybatis-config.xml
#远程对服务器配置连接数据库服务器
-bash: vim: 未找到命令 需要先安装vim
[root@localhost webapps]# yum install -y vim-common
[root@localhost webapps]# yum install -y vim-enhanced  #再安装一个增强的vim包
[root@localhost webapps]# vim ./imooc_oa/WEB-INF/classes/mybatis-config.xml
输入/root 进行全文查找 修改<property 中的value="jdbc:mysql:..." loacalhost改为自己网络ip:192.168.170.131
再修改一下下面的password:  Panchunyao123!
:wq
[root@localhost webapps]# cd ..
[root@localhost apache-tomcat-9.0.34]# vim ./conf/server.xml
/8080 搜索 <Connector port="80" 只保留80端口
/Host> 找到最下面 在上面一行加入
<Context path="/" docaBacs="imooc_oa"/> #将imooc_oa目录映射到根路径"/"
:wq

之后启动tomcat
[root@localhost apache-tomcat-9.0.34]# ./bin/startup.sh
[root@localhost apache-tomcat-9.0.34]# netstat -tulpn  #查看进程
tcp6        0        0 :::80        :::*        LISTEN        20303/java

用防火墙将80端口对外进行暴露
[root@localhost apache-tomcat-9.0.34]# firewall-cmd --zone=public --permanent --add-port=80/tcp
[root@localhost apache-tomcat-9.0.34]# firewall-cmd --reload  #重载

回到电脑浏览器输入:http://192.168.170.133/login.html

数据库安全【根据3306端口可以查到数据库根源】:对指定IP端口进行放行
[root@localhost ~]# firewall-cmd --zone=public --permanent --remove-port=3306/tcp
[root@localhost ~]# firewall-cmd --reload  #重载
[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family="ipv4" source address="192.168.170.133" port protocol="tcp" port="3306" accept "    #-rich-rule防火墙内部规则强大的定义表达式
[root@localhost ~]# firewall-cmd --reload  #重载
[root@localhost apache-tomcat-9.0.34]# ./bin/shutdown.sh
[root@localhost apache-tomcat-9.0.34]# ./bin/startup.sh


Redis (内存[非常快]数据库、非关系型数据库)

  • Redis是Key-Value型NoSQL数据库
  • Redis将数据存储在内存(RAM)中,同时也能持久化到磁盘
  • Redis常用于缓存,利用内存的高效提高程序的处理速度
Redis特点
  • 速度快
  • 广泛的语言支持
  • 持久化
  • 多种数据结构
  • 主从复制(多台Redis可以保持数据同步)
  • 分布式与高可用(7×24小时服务 淘宝/京东) 全国各地多个主机 降低网络传输时间

Redis的安装与启动

$ wegt http://download.redis.io/releases/redis-5.0.2.tar.gz
$ tar xzf redis-5.0.2.tar.gz
$ cd redis-5.0.2
$ make
[root@imooc imooc]# cd /usr/local
[root@imooc local]# ll
[root@imooc local]# mkdir redis
[root@imooc local]# ll
[root@imooc local]# cd redic
[root@imooc redic]# yum install gcc
[root@imooc redic]# wegt http://download.redis.io/releases/redis-5.0.2.tar.gz
[root@imooc redis]# tar xzf redis-5.0.2.tar.gz
[root@imooc redis]# cd redis-5.0.2
[root@imooc redis-5.0.2]# make
Hint: It's a good idea to run 'make test' ;)   安装成功
[root@imooc redis-5.0.2]# ll  #找redis.conf
总用量 240
-rw-rw-r--.  1 root root 85327 11月 22 2018 00-RELEASENOTES
-rw-rw-r--.  1 root root    53 11月 22 2018 BUGS
-rw-rw-r--.  1 root root  1894 11月 22 2018 CONTRIBUTING
-rw-rw-r--.  1 root root  1487 11月 22 2018 COPYING
drwxrwxr-x.  6 root root   192 12月  5 11:47 deps
-rw-rw-r--.  1 root root    11 11月 22 2018 INSTALL
-rw-rw-r--.  1 root root   151 11月 22 2018 Makefile
-rw-rw-r--.  1 root root  4223 11月 22 2018 MANIFESTO
-rw-rw-r--.  1 root root 20555 11月 22 2018 README.md
-rw-rw-r--.  1 root root 62155 11月 22 2018 redis.conf
-rwxrwxr-x.  1 root root   275 11月 22 2018 runtest
-rwxrwxr-x.  1 root root   280 11月 22 2018 runtest-cluster
-rwxrwxr-x.  1 root root   281 11月 22 2018 runtest-sentinel
-rw-rw-r--.  1 root root  9710 11月 22 2018 sentinel.conf
drwxrwxr-x.  3 root root  8192 12月  5 11:49 src
drwxrwxr-x. 10 root root   167 11月 22 2018 tests
drwxrwxr-x.  8 root root  4096 11月 22 2018 utils
[root@imooc redis-5.0.2]# cd src
[root@imooc src]# ll     #找redis- server启动目录

[root@imooc src]#cd ..
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
若端口被占用

找到redis-server的进程,然后杀死对应的进程,然后重新启动redis

>>> ps -ef | grep -i redis
root      3585 19590  0 10:36 pts/20   00:00:00 redis-server *:6379
user      3684  3663  0 10:38 pts/21   00:00:00 grep --color=auto -i redis
进程3585是redis的服务,

kill -9 3585
  • 在Windows系统安装Redis
https://github.com/microsoftarchive/redis/releases 下载后解压
打开cmd
C:\Users\Pluminary>d:
D:\>cd Redis-x64-3.0.504
D:\Redis-x64-3.0.504>dir
D:\Redis-x64-3.0.504>redis-server redis.windows.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 3.0.504 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 22828
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

Redis的常用基本配置

配置项 示例 说明
daemonize daemonize yes 是否启用后台运行, 默认no
port port 6379 设置端口号, 默认6379
logfile logfile 日志文件 设置日志文件
databases databases 255 设置redis数据库总量
dir dir 数据文件目录 设置数据文件存储目录
requirepass requirepass 12345 设置使用密码
守护进程方式启动Redis
[root@imooc ~]# cd /usr/local/redis/redis-5.0.2/
[root@imooc redis-5.0.2]# vim redis.conf    #打开后台启动
136行 daemonize no 改成 daemonize yes
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
关闭终端打开全新终端
[root@imooc ~]# netstat -tulpn
tcp        0        0        127.0.0.1:6379 ...     6338/./src/redis-se

如果关闭
kill -9 6338
redis使用
[root@imooc redis-5.0.2]# ./src/redis-cli    #执行redis内置指令
127.0.0.1:6379> ping        #启动成功
PONG
127.0.0.1:6379> exit        #退出
[root@imooc redis-5.0.2]# ./src/redis-cli shutdown  #更加安全的关闭redis

报错(添加log文件 将redis命令行的结果打印到log中)
https://blog.csdn.net/qq_46127735/article/details/113933690


为了保护安全将port从6379改为6380
[root@imooc redis-5.0.2]# vim redis.conf
/port 寻找92行
port 6380
[root@imooc redis-5.0.2]# ./src/redis-cli -p 6380
127.0.0.1:6380> select 0        #当前使用第几号数据库
127.0.0.1:6380> select 15
[root@imooc redis-5.0.2]# vim redis.conf
在186行 可以改变数据连接数量
databases 15→255
    
[root@imooc redis-5.0.2]# vim redis.conf
507行注释去掉 此行是输入密码
requirepass panchunyao123
再次登录就 
127.0.0.1:6380> auth panchunyao123

redis中有一个dump.rdb 全量备份 同时备份到磁盘中

Redis通用命令

命令 示例 说明
select select 0 选择0号数据库
set set name lily 设置key=name, value=lily
get get hello 获得key=hello结果
keys keys he* 根据Pattern表达查询符合条件的key
dbsize dbsize 返回key的总数
exists exists a 检查key=a是否存在
del del a 删除key=a的数据
expire expire hello 20 设置key=hello 20秒后过期
ttl ttl hello 查看key=a的过期剩余时间
[root@imooc redis-5.0.2]# ./src/redids-cli -p 6380   #重新连接到端口
127.0.0.1:6380> select 10
127.0.0.1:6380[10]> set name lily   #十号数据库中增加key=name value=lily
127.0.0.1:6380[10]> get name  => "lily"
127.0.0.1:6380[10]> select 9
127.0.0.1:6380[9]> get name =>(nil)
127.0.0.1:6380[9]> set name kitty
127.0.0.1:6380[9]> get name =>"kitty"
127.0.0.1:6380[9]> keys *  #列举表达式能匹配的所有key
127.0.0.1:6380[9]> set sex male
127.0.0.1:6380[9]> keys *
127.0.0.1:6380[9]> keys n* #模糊匹配表达式
127.0.0.1:6380[9]> dbsize  #显示当前数据库的总量 => 2个 => "name"  "sex"
127.0.0.1:6380[9]> del sex #删除 => 1 返回0则不存在
127.0.0.1:6380[9]> expire name 30   #生效开始之后30秒存活时间
127.0.0.1:6380[9]> ttl name #查看存活时间 时间一到自动清除     keys*中无name数据

Redis数据类型

  • String - 字符串类型 (String最大512mb 建议单个kv不超过100kb)
name Lily
counter 3321
sn 7361-7749
字符串命令
命令 示例 说明
get get hello 获取key=hello结果
set set hello world 设置key=hello, value=hello
mset mget mset hello world java best
mget hello java
一次性设置或者获取多个值
del del hello 删除key=hello
incr/decr incr count
decr count
key值自增/自减1
incrby/decrby incrby count 99
decrby count 99
自增自减指定步长
127.0.0.1:6380[9]> select 10 
127.0.0.1:6380[10]> set name lily
127.0.0.1:6380[10]> set sex 18
127.0.0.1:6380[10]> set birthday 1998-03-11
127.0.0.1:6380[10]> keys *
1) "birthday"  2)"name"  3)"sex"
127.0.0.1:6380[10]> mset name1 kitty sex1 20 birthday1 2001-03-02 #一次性设置多个键值对
127.0.0.1:6380[10]> mget name sex birthday  #一次性提取多个
127.0.0.1:6380[10]> clear  #当前屏幕清空
127.0.0.1:6380[10]> incr age  #将指定的数字自增+1
127.0.0.1:6380[10]> set age 20
127.0.0.1:6380[10]> keys age => "age"
127.0.0.1:6380[10]> get age => "20"
127.0.0.1:6380[10]> incr age => (integer) 22 #不可以自增字符串噢
127.0.0.1:6380[10]> decrby age 3 #对指定的key自减 => 20-3=17
127.0.0.1:6380[10]> del age #删除某个key
  • Hash - Hash类型
Hash类型用于存储结构化数据

↓↓↓ ↓↓↓ key = emp:1 ↓↓↓ ↓↓↓ 在value中又产生一个键值对[下面全是单个的emp:1的key值]

name smith
age 35
birthday 2001-02-02
height 178
命令 示例 说明
hget hget emp:1 age 获取hash中key=age的值
hset hset emp:1 age 23 设置hash中age=23
hmset
hmget
hgetall
hmset emp:1 age 30 name kaka
hmget emp:1 age name
hgetall emp:1
设置hash多个值
获取hash多个值
获取hash所有值
hdel hdel emp:1 age 删除emp:1的age
hexists hexists emp:1 name 检查是否存在
hlen hlen emp:1 获取指定长度
127.0.0.1:6380[1]> hset emp:1 name zhangsan       #设置某个hash值
127.0.0.1:6380[1]> hset emp:1 age 35
127.0.0.1:6380[1]> hset emp:1 birthday 2001-02-02
127.0.0.1:6380[1]> hset emp:1 height 178
127.0.0.1:6380[1]> keys * => "emp:1"
127.0.0.1:6380[1]> hget emp:1 name    #获取指定hash值 =>"zhangsan"
127.0.0.1:6380[1]> hget emp:1 age => "35"
127.0.0.1:6380[1]> hgetall emp:1 #提取所有的数据 

127.0.0.1:6380[1]> hmset emp:2 name lisa age 23 birthday 1990-05-03 height 165
127.0.0.1:6380[1]> hgetall emp:2
127.0.0.1:6380[1]> del emp:2     #删除整个对象
127.0.0.1:6380[1]> hlen emp:1 => (integer) 4     #代表在emp:1中有4个属性
127.0.0.1:6380[1]> hgetall emp:1 
127.0.0.1:6380[1]> hexists emp:1 name  #判断属性是否存在 是则返回1
  • List - 列表类型
    • List列表就是一系列字符串的”数组”, 按插入顺序排序
    • List列表最大长度为2的32次方-1, 可以包含40亿个元素
    List命令
    • rpush listkey c b a - 右侧插入 先c后b最后a
    • lpush listkey f e d - 左侧插入 先f后e最后d d e f c b a
    • rpop listkey - 右侧弹出 d e f c b
    • lpop listkey -左侧弹出 e f c b
127.0.0.1:6380[1]> rpush list c =>(integer) 1
127.0.0.1:6380[1]> lrange list 0 -1  #输出指定列表起始到结束范围内的所有元素 [开始 末尾]
127.0.0.1:6380[1]> rpush list b a =>(integer) 3
127.0.0.1:6380[1]> lrange list 0 -1 => "c" "b" "a"
127.0.0.1:6380[1]> lpush list f      #在左侧插入
127.0.0.1:6380[1]> lrange list 0 -1 => "f" "c" "b" "a"
127.0.0.1:6380[1]> lpush list b a
127.0.0.1:6380[1]> lrange list 0 -1 => "a" "b" "f" "c" "b" "a"
127.0.0.1:6380[1]> rpop list => "a"
127.0.0.1:6380[1]> lrange list 0 -1 => "a" "b" "f" "c" "b"
127.0.0.1:6380[1]> lpop list => "b" "f" "c" "b"
  • Set - 集合类型 Zset - 有序集合类型
    • Set集合是字符串的无序集合, 集合成员是唯一的
    • Zset集合是字符串的有序集合, 集合成员是唯一的
Set集合
127.0.0.1:6380[1]> sadd set1 a => 1
127.0.0.1:6380[1]> keys * => "set1" "emp:1" "list"
127.0.0.1:6380[1]> sadd set1 b => "b"
127.0.0.1:6380[1]> sadd set1 c => "c"
127.0.0.1:6380[1]> sadd set1 d => "d"
127.0.0.1:6380[1]> sadd set1 e => "e"
127.0.0.1:6380[1]> sadd set1 f => "f"
127.0.0.1:6380[1]> smembers set1 => "a" "b" "c" "d" "e" "f" "e"  #字母顺序打乱 乱序

127.0.0.1:6380[1]> sadd set2 d     #创建一个set2与set1有重叠
127.0.0.1:6380[1]> sadd set2 e
127.0.0.1:6380[1]> sadd set2 f
127.0.0.1:6380[1]> sadd set2 h
127.0.0.1:6380[1]> sadd set2 g
127.0.0.1:6380[1]> smembers set2 => "d" "h" "e" "f" "g"
127.0.0.1:6380[1]> sinter set1 set2 =>     "d" "f" "e"     #取其中的交集
127.0.0.1:6380[1]> sunion set1 set2 =>     "d" "g" "b" "c" "h" "f" "e" "a"    #取其中的并集[取并集并去除重复元素]
127.0.0.1:6380[1]> sdiff set1 set2 #寻找叉集(在set1有 在set2中没有[两个部分交集排除在外])
=> "a" "c" "b"
127.0.0.1:6380[1]> sdiff set2 set1 => "h" "g"
Zset集合
127.0.0.1:6380[1]> zadd zset1 100 a =>1
127.0.0.1:6380[1]> zadd zset1 101 b => 1
127.0.0.1:6380[1]> zrange zset1 0 -1 => "a" "b" #按照顺序排序
127.0.0.1:6380[1]> zadd zset1 99 c => 1  
127.0.0.1:6380[1]> zrange zset1 0 -1 => "c" "a" "b" #按照分数升序排列
127.0.0.1:6380[1]> zadd zset1 102 d
127.0.0.1:6380[1]> zadd zset1 103 e
127.0.0.1:6380[1]> zadd zset1 104 f
127.0.0.1:6380[1]> zrange zset1 0 -1 withscores #升序打印分数
127.0.0.1:6380[1]> zrangebyscore zset1 100 103 #符合score从100-103的名字

Java客户端-Jedis

  • Jedis是Java语言开发的Redis客户端工具包
  • Jedis只是对Redis命令的封装, 掌握Redis命令便可轻易上手
允许远程访问需要改文件
[root@imooc ~]# cd /usr/local/redis/redis-5.0.2/
[root@imooc redis-5.0.2]# vim redis.conf
第88行 protected-mode yes 将yes设置为no
第69行 bind 127.0.0.1 改为 bind 0.0.0.0     #四个0代表所有ip主机都可以访问进来【真正开发时要用特定的ip号】
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
[root@imooc redis-5.0.2]# netstat -tulpn | grep redis
[root@imooc redis-5.0.2]# firewall-cmd --zone=public --add-port=6379/tcp --permanent
[root@imooc redis-5.0.2]# firewall-cmd --reload
[root@imooc redis-5.0.2]# ifconfig => IP地址: 192.168.170.135

[root@imooc redis-5.0.2]# ./src/redis-cli -p 6379

报错连接超时:
Java远程连接Redis时出现: java.net.SocketTimeoutException: connect timed out的解决办法-CSDN博客
Java连接Redis connection timed out 报错的解决方法_caused by: io.netty.channel.abstractchannel$annota-CSDN博客

systemctl start firewalld  #开启防火墙
systemctl enable firewalld.service  #开机自启动防火墙

systemctl stop firewalld.service #关闭防火墙
重启 Redis 服务端
ps -ef|grep redis    #查看 Redis 进程 
kill -s 9 进程号      #杀死 Redis 进程
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf 
package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.List;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            jedis.auth("root");
            jedis.select(2);
            System.out.println("Redis连接成功");
            //字符串
            jedis.set("sn", "7781-9938");
            String sn = jedis.get("sn");
            System.out.println(sn);
            jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
            List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
            System.out.println(goods);
            Long num = jedis.incr("num");
            System.out.println(num);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}
pom.xml【两个jar:    jedis-2.9.0.jar        fastjson-1.2.62.jar】
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Jedis_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>
[root@imooc redis-5.0.2]# ./src/redis-cli
127.0.0.1:6379> auth root
127.0.0.1:6379> select 2
127.0.0.1:6379[2]> keys * => "sn"
127.0.0.1:6379[2]> get sn => "7781-9938"
【此处更换新的java代码再次插入 奶粉...】
------------------------------------------------------------------
jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
------------------------------------------------------------------
127.0.0.1:6379[2]> keys * => "sn" "num" "title"
127.0.0.1:6379[2]> get num => 21
127.0.0.1:6379[2]> get title => \xe5\xa9\xb4\xe5\xb9\xbc\xe5...

Jedis操作Hash类型

package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            jedis.auth("root");
            jedis.select(2);
            System.out.println("Redis连接成功");
            //字符串
            jedis.set("sn", "7781-9938");
            String sn = jedis.get("sn");
            System.out.println(sn);
            jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
            List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
            System.out.println(goods);
            Long num = jedis.incr("num");
            System.out.println(num);

            //Hash
            jedis.hset("student:3312", "name", "张晓明");
            String name = jedis.hget("student:3312", "name");
            System.out.println(name);

            Map<String,String> studentMap = new HashMap();
            studentMap.put("name", "李兰");
            studentMap.put("age", "18"); //所有数据类型都是String
            studentMap.put("id", "3313");
            jedis.hmset("student:3313", studentMap);
            Map<String,String> smap = jedis.hgetAll("student:3313");
            System.out.println(smap);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}

---------------------------------------------------------------
Redis连接成功
7781-9938
[7781-9938, 婴幼儿奶粉, 20]
21
张晓明
{name=李兰, age=18, id=3313}
127.0.0.1:6380[2]> hgetall student:3313

Jedis操作List类型

package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try { //List
            jedis.del("letter"); //要先删除不然lpop、rpop的时候会追加数据
            jedis.rpush("letter", new String[]{"d", "e", "f"});
            jedis.lpush("letter", new String[]{"c", "b", "a"});
            List<String> letter = jedis.lrange("letter", 0, -1);
            jedis.lpop("letter");
            jedis.rpop("letter");
            letter = jedis.lrange("letter", 0, -1);
            System.out.println(letter);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}
----————--——----------------------------------------------------
[a, b, c, d, e, f] => [b, c, d, e]

利用Jedis缓存数据 [放在内存处理 速度快]

缓存数据:资料不要太大 信息比较稳定更新次数较低
Goods.java
public class Goods {
    private Integer goodsId;
    private String goodsName;
    private String description;
    private Float price;
    Getter + Setter + Constructor[空+全]
}
CacheSample.java
package com.imooc.jedis;

import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;

public class CacheSample {
    public CacheSample(){ //数据初始化
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            List<Goods> goodsList = new ArrayList();
            goodsList.add(new Goods(8818,"红富士苹果","",3.5f));
            goodsList.add(new Goods(8819,"赣南脐橙","",5f));
            goodsList.add(new Goods(8820,"进口香蕉","",2f));
            //javabean序列化为json字符串保存到java里
            jedis.auth("root");
            jedis.select(3);
            for (Goods goods : goodsList){
                String json = JSON.toJSONString(goods);
                System.out.println(json);
                String key = "goods:" + goods.getGoodsId();
                jedis.set(key, json); //key + value[序列化为json]
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }

    public static void main(String[] args) {
        new CacheSample();
    }
}
________________________________________________________________
{"description":"","goodsId":8818,"goodsName":"红富士苹果","price":3.5}
{"description":"","goodsId":8819,"goodsName":"赣南脐橙","price":5.0}
{"description":"","goodsId":8820,"goodsName":"进口香蕉","price":2.0}
________________________________________________________________
127.0.0.1:6379[3]> keys *
127.0.0.1:6379[3]> "goods:8820" "goods:8819" "goods:8818"
127.0.0.1:6379[3]> get goods:8820

更新
CacheSample.java
package com.imooc.jedis;

import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CacheSample {
    public CacheSample(){ //数据初始化
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            List<Goods> goodsList = new ArrayList();
            goodsList.add(new Goods(8818,"红富士苹果","",3.5f));
            goodsList.add(new Goods(8819,"赣南脐橙","",5f));
            goodsList.add(new Goods(8820,"进口香蕉","",2f));
            //javabean序列化为json字符串保存到java里
            jedis.auth("root");
            jedis.select(3);
            for (Goods goods : goodsList){
                String json = JSON.toJSONString(goods);
                System.out.println(json);
                String key = "goods:" + goods.getGoodsId();
                jedis.set(key, json); //key + value[序列化为json]
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }

    public static void main(String[] args) {
        new CacheSample();
        System.out.println("请输入要查询的商品编号:");
        String goodsId = new Scanner(System.in).next();
        Jedis jedis = new Jedis("192.168.170.135");
        jedis.auth("root");
        jedis.select(3);
        String key = "goods:" + goodsId;
        if (jedis.exists(key)){
            String json = jedis.get(key);
            System.out.println(json);
            //由json转回到java对象
            Goods g = JSON.parseObject(json, Goods.class);
            System.out.println(g.getGoodsName());
            System.out.println(g.getPrice());
        }else{
            System.out.println("您输入的商品编号不存在,请重新输入!");
        }
    }
}
阅读全文

MyBatis实现OA系统项目实战

2023/11/7

慕课网办公OA平台

课程介绍
  • 需求说明与环境准备
  • 开发基于RBAC的访问控制模块
  • 开发多级请假审批流程

办公自动化OA系统

  • 办公自动化系统(Office Automation)是替代传统办公的解决方案
  • OA系统是利用软件技术构建的单位内部办公平台,用于辅助办公
  • 利用OA系统可将办公数据数字化,可扩大提高办公流程执行效率

项目需求

  • 慕课网办公OA系统要求采用多用户B/S架构设计开发
  • HR为每一位员工分配系统账户,员工用此账户登录系统
  • 公司采用分级定岗,从1-8依次提升,不同岗位薪资水平不同
    • 6级(含)以下员工为业务岗,对应人员执行公司业务事宜
    • 7-8级为管理岗,其中7级为部门经理8级为总经理
    • 业务岗与管理岗员工可用系统功能不同,要求允许灵活配置

请假流程

  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

搭建基础架构

框架&组件
  • MySQL 8
  • Mybatis 3.5
  • Alibaba Druid
  • Servlet 3.1
  • Freemarker 2.3
  • LayUl 2.5
工程结构
imooc-oa  eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构
com.imooc-oa //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

环境配置

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

配置pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>imooc-oa</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <!--Mybatis 框架-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--MySQL 8 JDBC驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--Druid数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!--Junit4单元测试框架-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--只参与Maven Test,不进行发布-->
            <scope>test</scope>
        </dependency>
        <!--Logback日志输出组件-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
<!--用Maven时必打的代码-->
                <!--利用Maven编译插件将编译级别提高至1.8,解决lambda表达式错误-->
                <groupId>org.apache.maven.plugins</groupId>
                <!--maven-compiler-plugin是Maven自带的编译插件-->
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <!--检查源码采用1.8规则,默认为1.5-->
                    <source>1.8</source>
                    <!--按1.8规则生成字节码-->
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
配置数据库连接池
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
            <dataSource type="POOLED">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>

开发Mybatis

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

test.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
    <select id="sample" resultType="string">
        select 'success'
    </select>
</mapper>
util-MybatisUtils.java
package util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
import java.util.function.Function;

public class MybatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static{
        Reader reader = null;
        try{
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }catch(IOException e){
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 执行SELECT查询SQL
     * @param func 要执行查询语句的代码块
     * @return 查询结果
     */
    //用于数据的查询[极大的简化查询] mybatis执行SQL时一定要有mapper的xml
    public static Object executeQuery(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            return obj;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }

    /**
     * 执行INSERT/UPDATE/DELETE写操作SQL
     * @param func 要执行的写操作代码块
     * @return 写操作后返回的结果
     */
    public static Object executeUpdate(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            sqlSession.commit();
            return obj;
        }catch (RuntimeException e){
            sqlSession.rollback();
            throw e;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }
}
MybatisUtilsTestor.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import util.MybatisUtils;

public class MybatisUtilsTestor {
//    @Test
//    public void testcase1(){
//        String result = (String)MybatisUtils.executeQuery(sqlSession -> {
//            String out = (String)sqlSession.selectOne("test.sample");
//            return out; //out会被retrun obj接收 返回Object
//        });
//        System.out.println(result);
//    }
    @Test
    public void testcase2(){
        String result = (String) MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        System.out.println(result);
    }
}

MyBatis整合Druid连接池 (自定义连接池)

重新整合mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.oa.datasource.DruidDataSourceFactory">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <!--连接池初始连接数-->
                <property name="initialSize" value="10"/>
                <!--连接池最大连接数-->
                <property name="maxActive" value="20"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>
com.imooc.oa.datasource.DruidDataSourceFactory
package com.imooc.oa.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;

public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    public DruidDataSourceFactory(){ //1.创造空的数据源对象
        // 2.调用setProperties读取xml对dataSource属性源进行设置
        this.dataSource = new DruidDataSource(); //表达数据源信息
    }
    //3.数据源需要额外设置要重写
    @Override
    public DataSource getDataSource() { //获取已经初始化的连接池进行返回
        try {
            ((DruidDataSource)this.dataSource).init(); //初始化Druid数据源
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return this.dataSource;
    }
}
Ctrl + Shift + N 文件查找对话框

整合Freemarker

pom.xml
    <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
web-WEB-INF-ftl-test.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>${result}</h1>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>freemaker</servlet-name>
        <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
        <init-param>
            <!--        定义模板的存储路径-->
            <param-name>TemplatePath</param-name>
            <param-value>/WEB-INF/ftl</param-value>
        </init-param>
        <init-param>
<!-- default_encoding用于设置读取ftl文件时采用的字符集,进而避免中文乱码的产生-->
            <param-name>default_encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>freemaker</servlet-name>
        <url-pattern>*.ftl</url-pattern>
    </servlet-mapping>
</web-app>
TestServlet.java
package com.imooc.oa.test;

import com.imooc.oa.util.MybatisUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "TestServlet", urlPatterns = "/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = (String)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        req.setAttribute("result",result);
        req.getRequestDispatcher("/test.ftl").forward(req,resp);
    }
}

RBAC(Role-Based Access Control)介绍

  • 基于**角色权限控制**(RBAC)是面向企业安全策略的访问控制方式

  • RBAC核心思想是将控制访问的资源与角色(Role)进行绑定

  • 系统的用户(User)与角色(Role)再进行绑定, 用户便拥有对应权限

一般主键cno或id都要设定字段类型为 BigInt
imooc-oa.sql

实现用户登录

基于LayUI开发登录页

LayUI前端框架

Layui - 经典开源模块化前端 UI 框架(官网文档镜像站) (layuiweb.com)

login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui-main/src/css/layui.css">
    <style>
        body {
            background-color: #f2f2f2;
        }

        .oa-container {
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center; margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <input type="password" id="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
</body>
</html>

实现用户登录-1

com.imooc.oa.entity.User
package com.imooc.oa.entity;

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    Getter + Setter
}
com.imooc.oa.dao.UserDao
package com.imooc.oa.dao;

import com.imooc.oa.entity.User;
import com.imooc.oa.util.MybatisUtils;

/**
 * 用户表
 */
public class UserDao {
    /**
     * 按照用户名查询用户表
     * @param username 用户名
     * @return User对象包含对应的用户信息,null则代表对象不存在
     */
    public User selectByUsername(String username){
        User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername",username));
        return user;
    }
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usermapper">
    <select id="selectByUsername" parameterType="String" resultType="com.imooc.oa.entity.User">
        select * from sys_user where username = #{value}
    </select>
</mapper>
mybatis-config.xml
<mappers>
    <mapper resource="mappers/test.xml"/>
    <mapper resource="mappers/user.xml"/>
</mappers>

Dao → Service

创建测试用例快捷键 Ctrl+Shift+T
UserSerive.java
package com.imooc.oa.serive;

import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.User;
import com.imooc.oa.serive.exception.BussinessException;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */ 
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
}
test/serive.UserServiceTest.java
package com.imooc.oa.serive;

import junit.framework.TestCase;
import org.junit.Test;

public class UserServiceTest extends TestCase {
    private UserService userService = new UserService();

    @Test
    public void testCheckLogin1() {
        userService.checkLogin("uu","1234");
    }
    @Test
    public void testCheckLogin2() {
        userService.checkLogin("m8","1234");
    }
    @Test
    public void testCheckLogin3() {
        userService.checkLogin("uu","test");
    }
}
serive.exception.BussinessException.java
package com.imooc.oa.serive.exception;

/**
 * 业务逻辑异常
 */
public class BussinessException extends RuntimeException{
    private String code; //异常编码,异常的以为标识
    private String message; //异常的具体文本消息
    public BussinessException(String code, String msg){
        super(code + ":" + msg);
        this.code = code;
        this.message = msg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

实现用户登录-2

com.imooc.oa.controller.LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑
            User user = userService.checkLogin(username, password);
            result.put("code", "0");
            result.put("message", "success");
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

实现用户登录-3

pom.xml
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.62</version>
</dependency>
login.xml实现增添表单校验
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>慕课网办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        body{
            background-color: #F2F2F2;
        }
        .oa-container{
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center;margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" lay-verify="required" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input" >
        </div>

        <div class="layui-form-item">
            <input type="password" id="password" lay-verify="required" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input" >
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>
    // 表单提交事件 表单输入校验 在上面加 lay-verify="requrired"
    layui.form.on("submit(login)" , function(formdata){//data参数包含了当前表单的数据
        console.log(formdata);
        //发送ajax请求进行登录校验
        layui.$.ajax({
            url : "/check_login",
            data : formdata.field, //提交表单数据
            type : "post",
            dataType : "json" ,
            success : function(json){
                console.log(json);
                if(json.code == "0"){ //登录校验成功 内置弹出层
                    layui.layer.msg("登录成功");
                }else{
                    layui.layer.msg(json.message);
                }
            }
        })
        return false;//submit提交事件返回true则表单提交,false则阻止表单提交
    })
</script>
</body>
</html>
</html>
后面通过Ajax[浏览器后台(上面return false)]请求向服务器发起异步通信获取校验是否通过

分析后台首页布局与设计

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <!--父节点-->
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块1</a>
                    <dl class="layui-nav-child module" data-node-id="1"></dl>
                </li>
                <!--子节点-->
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能1</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能2</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块2</a>
                    <dl class="layui-nav-child module" data-node-id="2"></dl>
                </li>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能4</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能5</a>
                </dd>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

动态显示功能菜单-1 【核心:rbac.xml

通过用户找到角色sys_user 再通过角色找到节点sys_role_user 接下来通过节点编号sys_role_node去获取与之对应的节点其他信息(三表关联)

xml→Dao→UserService

resources.mappers.rbac.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="rbacmapper">
    <select id="selectNodeByUserId" parameterType="Long" resultType="com.imooc.oa.entity.Node">
        select distinct n.*
        from
            sys_role_user ru, sys_role_node rn, sys_node n
        where
            ru.role_id = rn.role_id and user_id = #{value} and rn.node_id = n.node_id
        order by n.node_code
    </select>
</mapper>
mybatis-config.xml
    <mappers>
        <mapper resource="mappers/test.xml"/>
        <mapper resource="mappers/user.xml"/>
        <mapper resource="mappers/rbac.xml"/>
    </mappers>
imooc.oa.dao.RbacDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Node;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

public class RbacDao {
    public List<Node> selectNodeByUserId(Long userId){
        return (List)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectList("rbacmapper.selectNodeByUserId", userId));
    }
}
service.UserService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}
UserServiceTest.java
@Test
    public void selectNodeByUserId(){
        List<Node> nodeList = userService.selectNodeByUserId(2l);
        System.out.println(nodeList);
    }
Node.java
public class Node {
    private Long nodeId;
    private Integer nodeType;
    private String nodeName;
    private String url;
    private Integer nodeCode;
    private Long parentId;
    Setter + Getter
}

动态显示功能菜单-2 (不同用户登录不同功能)

修改LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑 不能直接 request.setAttribute 选更大的对象
            User user = userService.checkLogin(username, password);
            HttpSession session = request.getSession();
            //session种存在用户信息 向session存入登录用户信息,属性名:login_user
            session.setAttribute("login_user", user);
            result.put("code", "0");
            result.put("message", "success");
            result.put("redirect_url", "/index"); //url登陆成功直接返回客户端
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
oa.controller.IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
//接收数据传入
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        request.setAttribute("node_list",nodeList);
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
将index.html 变成 index.ftl并放在Web/WEB-INF/ftl/index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

Xml配置下实现Mapper接口 (登录用户所对应员工)

接口 xml mapper增加 employeeservice

index.ftl indexServlet 改index.ftl

增加自动化部门 entity.Department dao.创建接口DepartmentDao mappers.department.xml -config.xml注册 DepartmentSerive.java
IndexServlet.java index.ftl

entity.Employee.java
public class Employee{
    private Long employeeId;
    private String name;
    private Long departmentId;
    private String title;
    private Integer level;
    Getter+Setter
}
dao.EmployeeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mybatis-config.xml
<mapper resource="mappers/employee.xml"/>

index.ftl
 <!--右侧当前用户信息-->
   <ul class="layui-nav layui-layout-right">
       <li class="layui-nav-item">
           <a href="javascript:void(0)">
               <!--图标-->
              <span class="layui-icon layui-icon-user" style="font-size: 20px">
               </span>
               <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
增加自动化部门
entity.Department.java
public class Department {
    private Long departmentId;
    private String departmentName;
    Getter+Setter
}
dao.DepartmentDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mapper.department.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.DepartmentDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Department">
        select * from adm_department where department_id = #{value}
    </select>
</mapper>

mybatis-config.xml
<mapper resource="mappers/department.xml"/>
service.DepartmentServlet.java
package com.imooc.oa.service;

import com.imooc.oa.dao.DepartmentDao;
import com.imooc.oa.entity.Department;
import com.imooc.oa.util.MybatisUtils;

public class DepartmentService {
    /**
     * 按编号得到部门对象
     * @param departmentId 部门编号
     * @return 部门对象,null代表部门不存在
     */
    public Department selectById(Long departmentId){
        return (Department) MybatisUtils.executeQuery(
                sqlSession -> sqlSession.getMapper(DepartmentDao.class).selectById(departmentId));
    }
}
controller.indexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

基于MD5算法对密码加密

MD5摘要算法
  • MD5信息摘要算法广泛使用的密码散列函数
  • MD5可以产生出一个128位的散列值用于唯一标识源数据
  • 项目中通常使用MD5作为敏感数据的加密算法
MD5特点
  • 压缩性, MD5生成的摘要长度固定
  • 抗修改, 源数据哪怕有一个字节变化, MD5也会有巨大差异
  • 不可逆, 无法通过MD5反向推算源数据
Apache Commons Codec
  • Commons-Codec是Apache提供的编码/解码组件
  • 通过Commons-Codec可以轻易生成源数据的MD5摘要
  • MD5摘要方法: String md5 = DigestUtils.md5Hex (源数据)
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
}

敏感数据加盐混淆

md5utilstest userservice user

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    private Integer salt;
    Getter + Setter
}
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    /**
     * 对数据源加盐混淆后生成MD5摘要
     * @param source 源数据
     * @return MD5摘要
     */
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
    public static String md5Digest(String source, Integer salt){
        char[] ca = source.toCharArray(); //字符数组
        for (int i = 0; i < ca.length; i++) {
            ca[i] = (char)(ca[i] + salt);
        }
        String target = new String(ca);
//        System.out.println(target);
        String md5 = DigestUtils.md5Hex(target);
        return md5;
    }

    public static void main(String[] args) {
        System.out.println(MD5Utils.md5Digest("test", 188));
    }
}
修改UserService.java 中的密码校验
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MD5Utils;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        //对前台输入的密码加盐混淆后生成MD5,与保存在数据库中的MD5密码进行对比
        String md5 = MD5Utils.md5Digest(password, user.getSalt());
        if(!md5.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}

实现注销功能

loginServlet保存着数据 indexservlet中的session保存着数据
清除session

oa.controller.LogoutServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "LogoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().invalidate(); //会话注销
        response.sendRedirect("/login.html"); //跳转回去
    }
}
index.ftl
<!--注销按钮-->
<li class="layui-nav-item"><a href="/logout">注销</a></li>

请假流程数据库设计

开发多级审批流程
  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

工作流程表设计

请假单表LeaveForm → 审批任务流程表ProcessFlow → 消息通知表Notice

设计约束
  • 每一个请假单对应一个审批流程

  • 请假单创建后, 按业务规则生成部门经理、总经理审批任务

  • 审批任务的经办人只能审批自己辖区内的请假申请(总裁办可以审批所有 软件只能审批软件)

  • 所有审批任务”通过”, 代表请假已经批准

  • 任意审批任务”驳回”操作, 其余审批任务取消, 请假申请驳回

  • 请假流程中注意节点产生的操作都要生成对应的系统通知

实现Dao与数据交互

entity 数据新增接口dao(依靠Mybatis) 增加mapper 创造LeaveFormDaoTest
ProcessFlowDao + Test mapper
NoticeDao

entity.LeaveForm.java
public class LeaveForm {
    private Long formId;
    private Long employeeId;
    private Integer formType;
    private Date startTime;
    private Date endTime;
    private String reason;
    private Date createTime;
    private String state;
    Getter + Setter
}
dao.LeaveForm.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
}
mybatis-config.xml
<mapper resource="mappers/leave_form.xml"/>
test.dao.LeaveFormDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class LeaveFormDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(4L);//员工编号
            form.setFormType(1); //事假
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date startTime = null; //起始时间
            Date endTime = null; //结束时间
            try {
                startTime = sdf.parse("2020-03-25 08:00:00");
                endTime = sdf.parse("2020-04-01 18:00:00");
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            form.setStartTime(startTime);
            form.setEndTime(endTime);
            form.setReason("回家探亲"); //请假事由
            form.setCreateTime(new Date()); //创建时间
            form.setState("processing"); //当前状态
            dao.insert(form);
            return null;
        });
    }

}

entity.ProcessFlow.java
package com.imooc.oa.entity;

import java.util.Date;

public class ProcessFlow {
    private Long processId;
    private Long formId;
    private Long operatorId;
    private String action;
    private String result;
    private String reason;
    private Date createTime;
    private Date auditTime;
    private Integer orderNo;
    private String state;
    private Integer isLast;
    Setter + Getter
}
dao.ProcessFlowDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;

public interface ProcessFlowDao {
    public void insert(ProcessFlow processFlow);
}
mybatis-config.xml
<mapper resource="mappers/process_flow.xml"/>
test.dao.ProcessFlowDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;

import java.util.Date;

public class ProcessFlowDaoTest extends TestCase {
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            ProcessFlowDao dao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow = new ProcessFlow();
            flow.setFormId(31L);
            flow.setOperatorId(21L);
            flow.setAction("audit");
            flow.setReason("approved");
            flow.setReason("同意");
            flow.setCreateTime(new Date());
            flow.setAuditTime(new Date());
            flow.setOrderNo(1);
            flow.setState("ready");
            flow.setIsLast(1);
            dao.insert(flow);
            return null;
        });
    }
}

entity.Notice.java
public class Notice {
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    Getter + Setter
}
dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

public interface NoticeDao {
    public void insert(Notice notice);
}
mybatis-config.xml
<mapper resource="mappers/notice.xml"/>
test.dao.NoticeDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.util.Date;

public class NoticeDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao dao = sqlSession.getMapper(NoticeDao.class);
            Notice notice = new Notice();
            notice.setReceiverId(21L);
            notice.setContent("测试消息");
            notice.setCreateTime(new Date());
            dao.insert(notice);
            return null;
        });
    }
}

实现请假申请业务逻辑-1

LeaveFormService employee.xml{动态审批sql}

employee.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.EmployeeDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where employee_id = #{value}
    </select>
    <select id="selectLeader" parameterType="com.imooc.oa.entity.Employee" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where
            <if test="emp.level &lt; 7">
                level = 7 and department_id = #{emp.departmentId}
            </if>
            <if test="emp.level == 7">
                level = 8;
            </if>
            <if test="emp.level == 8">
                employee_id = #{emp.employeeId}
            </if>
    </select>
</mapper>
service.LeaveFormService.java (重要业务走向代码不放在程序中)
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;

import java.util.Date;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
              //3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanage = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanage.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanage);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }
            return form;
        });
        return savedForm;
    }
} 
service.BussinessConstants.java
package com.imooc.oa.service;

public class BussinessConstants {
    public static final int MANAGER_AUDIT_HOURS = 36; //总经理请假审批时间阈值
}

ctrl+shift+t 生成测试用例

Test.service.LeaveFormServiceTest.java
package com.imooc.oa.service;

import com.imooc.oa.entity.LeaveForm;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.junit.Assert.*;

public class LeaveFormServiceTest {
    LeaveFormService leaveFormService = new LeaveFormService();

    /**
     * 市场部员工请假单(72小时以上)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm1() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1); //事假
        form.setReason("市场部员工请假单(72小时以上)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 市场部员工请假单(72小时内)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020032718"));
        form.setFormType(1);
        form.setReason("市场部员工请假单(72小时内)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 研发部部门经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm3() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(2l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("研发部部门经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 总经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm4() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(1l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("总经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }
}

实现请假申请控制

Servlet 前后端整体交互(底层)

leaveformservlet

controller.LeaveFormServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.LeaveFormService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LeaveFormServlet", urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
    private LeaveFormService leaveFormService = new LeaveFormService();
    private Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){

        }
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        this.doPost(request,response);
    }

    /**
     * 创建请假单
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        String formType = request.getParameter("formType");
        String strStartTime = request.getParameter("startTime");
        String strEndTime = request.getParameter("endTime");
        String reason = request.getParameter("reason");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
        Map result = new HashMap();
        try {
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(user.getEmployeeId());
            form.setStartTime(sdf.parse(strStartTime));
            form.setEndTime(sdf.parse(strEndTime));
            form.setFormType(Integer.parseInt(formType));
            form.setReason(reason);
            form.setCreateTime(new Date());
            //2.调用业务逻辑方法
            leaveFormService.createLeaveForm(form);
            result.put("code", "0");
            result.put("message", "success");
        } catch (Exception e){
            logger.error("请假申请异常",e);
            result.put("code", e.getClass().getSimpleName());
            result.put("message", e.getMessage());
        }
        //3.组织相应数据
        String json = JSON.toJSONString(result); //将result转换为字符串
        response.getWriter().println(json);
    }
}

完整实现请假申请功能

controller.ForwardServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 页面跳转Servlet
 */
@WebServlet(name="ForwardServlet", urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String uri = request.getRequestURI();
        /** 动态提取 把第一个/抛出外 再去寻找第一个‘/’
         * /forward/form
         * /forward/a/b/c/form
         */
        String subUri = uri.substring(1);
        String page = subUri.substring(subUri.indexOf("/"));
        request.getRequestDispatcher(page + ".ftl").forward(request,response); //扩展名 + web.xml映射路径
    }
}
form.html 变换为 form.ftl 放在ftl内

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>请假申请</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        /*表单容器*/
        .ns-container {
            position: absolute;
            width: 500px;
            height: 450px;
            top: 150px;
            left: 50%;
            margin-left: -250px;
            padding: 20px;
            box-sizing: border-box;
            border: 1px solid #cccccc;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>请假申请</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>
<div class="ns-container">
    <h1 style="text-align: center;margin-bottom: 20px">请假申请单</h1>
    <form class="layui-form">
        <!--基本信息-->
        <div class="layui-form-item">
            <label class="layui-form-label">部门</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    研发部
                </div>

            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">申请人</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    ${current_employee.name}[${current_employee.title}]
                </div>

            </div>
        </div>
        <!--请假类型下拉框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假类别</label>
            <div class="layui-input-block layui-col-space5">
                    <select name="formType" lay-verify="required" lay-filter="cityCode">
                        <option value="1">事假</option>
                        <option value="2">病假</option>
                        <option value="3">工伤假</option>
                        <option value="4">婚嫁</option>
                        <option value="5">产假</option>
                        <option value="6">丧假</option>
                    </select>
            </div>
        </div>
        
        <!--请假时长日期选择框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假时长</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="leaveRange" type="text" class="layui-input" id="daterange" placeholder=" - ">
                    <input id="startTime" name="startTime" type="hidden">
                    <input id="endTime" name="endTime" type="hidden">
            </div>
        </div>

        <!--请假事由-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假事由</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="reason" type="text"  lay-verify="required|mobile" placeholder="" autocomplete="off" class="layui-input">
            </div>
        </div>

        <!--提交按钮-->
        <div class="layui-form-item " style="text-align: center">
                <button class="layui-btn" type="button" lay-submit lay-filter="sub">立即申请</button>
        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<!--Sweetalert2对话框-->
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
        var layDate = layui.laydate; //Layui日期选择框JS对象
        var layForm = layui.form; //layui表单对象
        var $ = layui.$; //jQuery对象
        //日期时间范围
        layDate.render({
            elem: '#daterange'  //daterange渲染成日期选择框
            ,type: 'datetime'
            ,range: true
            ,format: 'yyyy年M月d日H时'
            ,done: function(value, start, end){
                //选择日期后出发的时间,设置startTime与endTime隐藏域
                var startTime = start.year + "-" + start.month + "-" + start.date + "-" + start.hours;
                var endTime = end.year + "-" + end.month + "-" + end.date + "-" + end.hours;
                console.info("请假开始时间",startTime);
                $("#startTime").val(startTime);
                console.info("请假结束时间",endTime);
                $("#endTime").val(endTime);
            }
        });

        //表单提交时间
        layForm.on('submit(sub)', function(data){
            console.info("向服务器提交的表单数据",data.field);
            $.post("/leave/create",data.field,function (json) {
                console.info("服务器返回数值",json);
                if(json.code == "0"){
                    /*SweetAlert2确定对话框*/
                    swal({
                        type: 'success',
                        html: "<h2>请假单已提交,等待上级审批</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            },"json");
            return false;
        });

</script>
</body>
</html>
把localhost/index 主页添加数据
index.ftl中的47行到50行
 <!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
 <a href="#{node.url}" target="ifmMain">${node.nodeName}</a>
</dd>

请假审批功能

leave_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.LeaveFormDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.LeaveForm"
        useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id">
        INSERT INTO adm_leave_form( employee_id, form_type, start_time, end_time, reason, create_time, state)
        VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state})
    </insert>
    <select id="selectByParams" parameterType="java.util.Map" resultType="java.util.Map">
        select f.* ,e.name , d.*
        from
          adm_leave_form f,adm_process_flow pf , adm_employee e , adm_department d
        where
          f.form_id = pf.form_id
          and f.employee_id = e.employee_id
          and e.department_id = d.department_id
          and pf.state = #{pf_state} and pf.operator_id = #{pf_operator_id}
    </select>
<!--    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.LeaveForm">-->
<!--        select * from adm_leave_form where form_id = #{value}-->
<!--    </select>-->

<!--    <update id="update" parameterType="com.imooc.oa.entity.LeaveForm">-->
<!--        UPDATE adm_leave_form SET employee_id = #{employeeId} , form_type = #{formType}, start_time = #{startTime}, end_time = #{endTime}, reason = #{reason}, state = #{state} ,create_time = #{createTime} WHERE form_id = #{formId}-->
<!--    </update>-->
</mapper>
dao.LeaveFormDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
    public List<Map> selectByParams(@Param("pf_state") String pfState , @Param("pf_operator_id") Long operatorId);
} 
Test
  @Test
    public void testSelectByParams(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> list = dao.selectByParams("process", 21L);
            System.out.println(list);
            return list;
        });
    }
controller.LeaveFormServlet.java
   /**
     * 查询需要审核的请假单列表
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("login_user");
        List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());
        Map result = new HashMap();
        result.put("code","0"); //服务器处理成功
        result.put("msg",""); //服务器返回具体处理消息
        result.put("count", formList.size()); //数据总数
        result.put("data", formList); //当前显示的对象页表
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

实现待审批请假列表

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>请假审批</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        .form-item{
            padding: 10px;
        }
        .form-item-value{
            padding: 10px;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h1>请假审批</h1>
    </blockquote>
    <!--待审批列表-->
    <table id="grdFormList" lay-filter="grdFormList"></table>
</div>
<!--请假详情对话框-->
<div id="divDialog" style="display: none;padding: 10px">
    <form class="layui-form">

        <div class="layui-form-item">
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">部门</div>
                <div class="layui-col-xs4 form-item-value" id="dname"></div>
                <div class="layui-col-xs2 form-item">姓名</div>
                <div class="layui-col-xs4 form-item-value" id="name"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">起始时间</div>
                <div class="layui-col-xs4 form-item-value" id="startTime"></div>
                <div class="layui-col-xs2 form-item">结束时间</div>
                <div class="layui-col-xs4 form-item-value" id="endTime"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">请假原因</div>
                <div class="layui-col-xs10 form-item-value" id="reason"></div>
            </div>
            <!--表单Id-->
            <input type="hidden" name="formId" id="formId">
            <!--审批结果-->
            <select name="result" lay-verfity="required">
                <option value="approved">同意</option>
                <option value="refused">驳回</option>
            </select>
        </div>
        <div class="layui-form-item">
            <!--审批意见-->
            <input type="text" name="reason" placeholder="请输入审批意见"
                   autocomplete="off" class="layui-input"/>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid " lay-submit lay-filter="audit">确认提交</button>

        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
    var $ = layui.$;
    //将毫秒数转换为"yyyy-MM-dd HH时"字符串格式
    function formatDate(time){
        var newDate = new Date(time);
        return newDate.getFullYear() + "-" +
            (newDate.getMonth() + 1) + "-" + newDate.getDate()
            + " " + newDate.getHours() + "时";
    }
    // 将table渲染为数据表格
    layui.table.render({
        elem : "#grdFormList" , //选择器
        id : "grdFormList" , //id
        url : "/leave/list" , //ajax请求url
        page : false , //是否分页 true-是 false-否
        cols :[[ //列描述
            {title : "" , width:70 , style : "height:60px" , type:"numbers"}, // numbers代表序号列
            {field : "create_time" , title : "申请时间" , width : 150 , templet: function (d) {
                //templet代表对数据进行加工后再显示
                return formatDate(d.create_time)
            }},
            {field : "form_type" , title : "类型" , width : 100 , templet: function(d){
                switch (d.form_type) {
                    case 1:
                        return "事假";
                    case 2:
                        return "病假";
                    case 3:
                        return "工伤假";
                    case 4:
                        return "婚假";
                    case 5:
                        return "产假";
                    case 6:
                        return "丧假";
                }
            }},
            {field : "department_name" , title : "部门" , width : 100},
            {field : "name" , title : "员工" , width : 100},
            {field : "start_time" , title : "起始时间" , width : 150, templet: function (d) {
                    return formatDate(d.start_time)
                }},
            {field : "end_time" , title : "结束时间" , width : 150 , templet: function (d) {
                    return formatDate(d.end_time)
                }},
            {field : "reason" , title : "请假原因" , width : 350 },
            {title : "" , width:150 ,type:"space" , templet : function(d){
                var strRec = JSON.stringify(d);
                console.info("请假单数据", d);
                console.info("请假单数据", strRec);
                //将请假单数据存放至data-laf属性中
                return "<button class='layui-btn layui-btn-danger layui-btn-sm btn-audit' data-laf=" + strRec + " >审批</button>";
            }}
        ]]
    })

    // 绑定每一行的审批按钮
    $(document).on("click" , ".btn-audit" , function(){
        //初始化表单
        $("#divDialog form")[0].reset();
        $("#divDialog form form-item-value").text("");
        //获取当前点击按钮的请假单数据,回填至显示项 json对象 内置数据显示页面
        var laf = $(this).data("laf");
        $("#dname").text(laf.department_name);
        $("#name").text(laf.name);
        $("#startTime").text(formatDate(laf.start_time));
        $("#endTime").text(formatDate(laf.end_time));
        $("#reason").text(laf.reason);
        $("#formId").val(laf.form_id);
        //弹出layui对话框
        layui.layer.open({
            type : "1" , //页面层
            title : "请假审批" , //标题
            content : $("#divDialog") , //指定对话框容器对象
            area : ["500px" , "400px"] , //尺寸
            end : function(){ //销毁后触发事件
                $("#divDialog").hide();
            }
        })
    })
    /**
     * 提交审批数据 本质:发送Ajax请求
     */
    layui.form.on("submit(audit)" , function(data){
        $.ajax({
            url : "/leave/audit", //审核URL
            data : data.field ,
            type : "post" ,
            dataType : "json" ,
            success: function (json) {
                //关闭所有layui对话框
                layui.layer.closeAll();
                //显示处理结果
                if(json.code == "0"){
                    swal({
                        type: 'success',
                        html: "<h2>请假已审批完毕</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            }
        })
        return false;
    })

</script>
</body>
</html>

实现审批业务逻辑

service.LeaveFormService.java
 public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                } else if (result.equals("refused")) {
                    //4.如果当前任务不是最后一个切点且审核驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for (ProcessFlow p : readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                }
            }
            return null;
        });
    }
test.LeaveFormServiceTest.java (在数据库中增加新建查询 导入训练素材的 请假单审核测试数据.sql)
   /**
     * 请假3天以上,部门经理审批通过
     */
    @Test
    public void audit1(){
        leaveFormService.audit(31l,2l,"approved","祝早日康复");
    }

    /**
     * 请假3天以上,部门经理审批驳回
     */
    @Test
    public void audit2(){
        leaveFormService.audit(32l,2l,"refused","工期紧张,请勿拖延");
    }

    /**
     * 部门经理请假,总经理审批通过
     */
    @Test
    public void audit3(){
        leaveFormService.audit(33l,1l,"approved","同意");
    }

完整实现请假审批

controller.LeaveFormServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){
            this.create(request,response);
        }else if (methodName.equals("list")){
            this.getLeaveFormList(request,response);
        } else if (methodName.equals("audit")) {
            this.audit(request,response);
        }
    }
...
...
...
  /**
     * 处理审批操作
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String formId = request.getParameter("formId");
        String result = request.getParameter("result");
        String reason = request.getParameter("reason");
        User user = (User) request.getSession().getAttribute("login_user");
        Map mpResult = new HashMap();
        try {
            leaveFormService.audit(Long.parseLong(formId), user.getEmployeeId(), result,reason);
            mpResult.put("code", "0");
            mpResult.put("message", "success");

        } catch (Exception e) {
            logger.error("请假单审核失败", e);
            mpResult.put("code", e.getClass().getSimpleName());
            mpResult.put("message", e.getMessage());
        }
        String json = JSON.toJSONString(mpResult);
        response.getWriter().println(json);
    }

实现系统消息业务逻辑

entity.Notice.java
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    public Notice(){

    }
    public Notice(Long receiverId, String content){
        this.receiverId = receiverId;
        this.content = content;
        this.createTime = new Date();
    }
    Getter + Setter
service.LeaveFormService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MybatisUtils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
                // 3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
                NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanager = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanager.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanager);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知部门经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(dmanager.getEmployeeId(),noticeContent));
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知总经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(manager.getEmployeeId(),noticeContent));
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过." ,
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
            }
            return form;
        });
        return savedForm;
    }
    /**
     * 获取指定任务状态及指定经办人对应的请假单列表
     * @param pfState ProcessFlow任务状态
     * @param operatorId 经办人编号
     * @return 请假单及相关数据列表
     */
    public List<Map> getLeaveFormList(String pfState, Long operatorId){
        return (List<Map>)MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> formList = dao.selectByParams(pfState, operatorId);
            return formList;
        });
    }
    /**
     * 审核请假单
     * @param formId 表单编号
     * @param operatorId 经办人(当前登录员工)
     * @param result 审批结果
     * @param reason 审批意见
     */
    public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());//表单提交人信息
            Employee operator = employeeDao.selectById(operatorId);//任务经办人信息
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
                String strResult = null;
                if (result.equals("aproved")){
                    strResult = "批准";
                } else if (result.equals("refused")) {
                    strResult = "驳回";
                }
                String noticeContent = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束"
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                        operator.getTitle(),operator.getName(), //批准\驳回
                        strResult,reason);//发给表单提交人的通知
                noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent));
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                    //消息1: 通知表单提交人,部门经理已经审批通过,交由上级继续审批
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s ,请继续等待上级审批" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知总经理有新的审批任务
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()));
                    noticeDao.insert(new Notice(readyProcess.getOperatorId(),noticeContent2));

                    //消息3: 通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
                    String noticeContent3 = String.format("%s-%s提起请假申请[%s-%s]您已批准,审批意见:%s,申请转至上级领导继续审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent3));
                } else if(result.equals("refused")) {
                    //4.如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for(ProcessFlow p:readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                    //消息1: 通知申请人表单已被驳回
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已驳回,审批意见:%s,审批流程已结束" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知经办人表单"您已驳回"
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s]您已驳回,审批意见:%s,审批流程已结束" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent2));
                }
            }
            return null;
        });

完整实现系统消息功能

dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

import java.util.List;

public interface NoticeDao {
    public void insert(Notice notice);
    public List<Notice> selectByReceiverId(Long receiverId);
}
service.NoticeService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

/**
 * 消息服务
 */
public class NoticeService {
    /**
     * 查询指定员工的系统消息
     * @param receiverId
     * @return 最近100条消息列表
     */
    public List<Notice> getNoticeList(Long receiverId){
        return (List) MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            return noticeDao.selectByReceiverId(receiverId);
        });
    }
}
controller.NoticeServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.NoticeService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "NoticeServlet" , urlPatterns = "/notice/list")
public class NoticeServlet extends HttpServlet {
    private NoticeService noticeService = new NoticeService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User)request.getSession().getAttribute("login_user");
        List<Notice> noticeList = noticeService.getNoticeList(user.getEmployeeId());
        Map result = new HashMap<>();
        result.put("code", "0");
        result.put("msg", "");
        result.put("count", noticeList.size());
        result.put("data", noticeList);
        String json = JSON.toJSONString(result);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println(json);
    }
}
resources.mappers.notice.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.NoticeDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.Notice"
            useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id">
        INSERT INTO sys_notice( receiver_id, content, create_time) VALUES (#{receiverId}, #{content}, #{createTime})
    </insert>

    <select id="selectByReceiverId" parameterType="Long" resultType="com.imooc.oa.entity.Notice">
        select * from sys_notice where receiver_id = #{value} order by create_time desc limit 0,100
    </select>
</mapper>
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="/logout">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="${node.url}" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" src="/forward/notice" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>
notice.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>系统通知</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>系统通知</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>

<script src="/resources/layui/layui.all.js"></script>

<script>
    layui.table.render({
        elem : "#grdNoticeList" ,
        id : "grdNoticeList" ,
        url : "/notice/list" ,
        page : false ,
        cols :[[
            {field : "" , title : "序号" , width:"10%" , style : "height:60px" , type:"numbers"},
            {field : "create_time" , title : "通知时间" , width : "20%" , templet: function (d) {
                    var newDate = new Date(d.createTime);
                    return newDate.getFullYear() + "-" +
                        (newDate.getMonth() + 1) + "-" + newDate.getDate()
                        + " " + newDate.getHours() + ":" + newDate.getMinutes() + ":" + newDate.getSeconds();
                }},
            {field : "content" , title : "通知内容" , width : "60%"}
        ]]
    })

</script>
</body>
</html>
阅读全文

MyBatis基础与进阶

2023/10/30

MyBatis[ORM]框架基础

软件开发中框架

  • 框架式可被应用开发者定制的应用骨架
  • 框架是一种规则,保证开发者遵循相同的方式开发程序
  • 框架提倡”不要重复造轮子”,对基础功能进行封装

框架的优点

  • 极大提高了开发效率
  • 统一的编码规则,利于团队管理
  • 灵活配置的应用,拥有更好的维护性

SSM开发框架

  • Spring对象容器框架(框架的框架) [提供底层对象的管理]
  • SpringMVC替代servlet更有效的开发 [提供Web界面的交互]
  • MyBatis简化数据库的开发 [数据库增删改查便捷操作]

MyBatis

  • MyBatis是优秀的持久层框架(将内存中的数据保存到数据库中 以防止重启后数据丢失)
  • MyBatis使用XML将SQL与程序解耦,便于维护[改代码 改xml更方便]
  • MyBatis是JDBC的延伸

MyBatis开发流程【推荐使用Maven】

  • 引入MyBatis依赖
  • 创建核心配置文件
  • 创建实体(Entity)类
  • 创建Mapper映射文件
  • 初始化SessionFactory(绘画工厂 读取配置文件 加载Mapper映射)
  • 利用SqlSession对象操作数据

单元测试与JUnit4

单元测试(用于测试方法的方法)
  • 单元测试是指对软件中的最小可测试单元进行检查和验证
  • 测试用例是指写一段代码对已有功能(方法)进行校验
  • JUnit4是Java中最著名的单元测试工具,主流IDE内置支持

JUnit4使用方法

  • 引入JUnit Jar包或增加Maven依赖
  • 编写测试用例验证目标方法是否正确运行
  • 在测试用例上增加**@Test**注解开始单元测试

如果插件plug报错 点settings 找到 Build,Execution,Deployment → Build Tools → Maven 修改下面的三个地址到Maven

Maven home path: D:/apache-maven-3.9.5
User settings files: D:\apache-maven-3.9.5\conf\settings.xml
Local repository: D:\apache-maven-3.9.5\repository

Maven工程有专门测试用例的 test包
方法命名:在原方法前增加test前缀 Class命名:在原有命名后增加Test

快速生成类的测试用例 => 选中类 → Code → Generate → Test
按住Ctrl可以多选执行多个测试用例类
如果想运行所有测试用例类 工程上点右键 → Run ‘All Test’

Calculator.java
package org.example;

public class Calculator {
    public int add(int a, int b){
        return a + b;
    }
    public int substract(int a, int b){
        return a - b;
    }
    public int multiply(int a, int b){
        return a * b;
    }
    public float divide(int a, int b){
        if (b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return (a*1f) / b;
    }
}
CalculatorTest.java
import org.example.Calculator;
import org.junit.Test;

public class CalculatorTest {
    private Calculator cal = new Calculator();
    @Test
    public void testAdd(){
        int result = cal.add(1,2);
        System.out.println(result);
    }
    @Test
    public void testSubstract(){
        int result = cal.substract(1,2);
        System.out.println(result);
    }
    @Test
    public void testMultiply(){
        int result = cal.multiply(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide(){
        float result = cal.divide(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide1(){
        float result = cal.divide(1,0);
        System.out.println(result);
    }
}

MyBatis环境配置

mybatis-config.xml
  • MyBatis采用XML格式配置数据库环境信息
  • MyBatis环境配置标签**< environment >**
  • environment包含数据库驱动、URL、用户名与密码
mybatis-config.xml
<!--配置环境,不同的环境不同的id名字-->
<enviroment id="dev">
    <!--采用JDBC方式对数据库事务进行commit/rollback-->
    <transactionManager type="JDBC"></transactionManager>
    <!--采用连接池方式管理数据库连接-->
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
</enviroment>
<groupId>org.example</groupId>
    <artifactId>JUnit4_Maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>http://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

可以在右侧加载内置Database数据库

<configuration>
    <environments default=""> 
        <environment id="">
            <transactionManager type="JDBC"></transactionManager> 
            <dataSource type=""></dataSource>
        </environment>
    </environments>
</configuration>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type=""></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTFF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlSessionFactory

  • SqlSessionFactory是MyBatis的核心对象
  • 用于初始化MyBatis, 创建SqlSession对象
  • SqlSession对象提供了数据表CRUD对应方法
  • 保证SqlSessionFactory在应用中全局唯一
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
test-MyBatisTestor.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
}
保证SqlSessionFactory在应用中全局唯一 [创建一个工具类]

static块用于初始化静态对象

MyBatisUtils.java
package com.imooc.mybatis.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    public static void closeSession(SqlSession session){
        if (session != null){
            session.close();
        }
    }
}
MyBatisTestor.java
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    @Test
    public void testMyBatisUtils() throws Exception{
        SqlSession sqlSession = null;
        try{
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
}

MyBatis数据查询

用测试类去测试代码
test-java-MyBatisTestor.java
import com.imooc.mybatis.MyBatisUtils;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    /**
     * MyBatisUtils使用指南
     * @throws Exception
     */
    @Test
    public void testMyBatisUtils() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

    /**
     * select查询语句执行
     * @throws Exception
     */
    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectAll");
            for(Goods g : list){
                System.out.println(g.getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
MyBatis数据查询步骤
  • 创建实体类(Entity) /工具类(Utils)
Goods.java
private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter+Setter...
MyBatisUtils.java
package com.imooc.mybatis;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * openSession 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的SqlSession对象
     * @param session 准备释放SqlSession对象
     */
    public static void closeSession(SqlSession session){
        if(session != null){
            session.close();
        }
    }
}
  • 创建Mapper XML (在resources下创建) 表属性和字段一一对应

若要让mybatis认识xml 要在mybatis-config.xml里面声明 增加< mappers >

goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
</configuration>
  • 编写< select >SQL标签
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
  • 开启驼峰命名映射
<settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  • 新增< mapper >
mybatis-config.xml
<mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
  • SqlSession执行select语句

SQL传参[动态传入数据]

goods.xml
<select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where goods_id = #{value}
</select>

MyBatisTestor.java
@Test
    public void testSelectById() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1602);
            System.out.println(goods.getTitle());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
goods.xml
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
        
MyBatisTestor.java (多参数传递要指定Map接口)
@Test
    public void selectByPriceRange() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("min", 100);
            param.put("max", 500);
            param.put("limit", 10);
            List<Goods> list = session.selectList("goods.selectByPriceRange",param);
            for (Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

获取多表关联查询结果[Map]

LinkedHashMap链表形式的HashMap不会出现乱序

利用LinkedHashMap保存多表关联结果
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名,value是字段对应的值,字段类型根据表结构进行自动判断
优点:易于扩展,易于使用
缺点:太过灵活,无法进行编译时检查

goods.xml
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
</mapper>
MyBatisTestor.java [map类 或 实体类]
@Test
    public void selectGoodsMap() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for (Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
}

ResultMap结果映射[多表查询结果 多人协作首选]

把复杂的查询结果映射成DTO对象来进行保存 调用的时候轻松获得属性 [书写大量的映射规则]
  • ResultMap可以将查询结果映射为复杂类型的Java对象
  • ResultMap适用于Java对象保存多表关联结果
  • ResultMap支持对象关联查询等高级特性

结果用java对象进行保存 dto包[GoodsDTO]是数据传输对象包[扩展包] 为了扩展的需要可以扩展

结果映射规则进行赋值 转换为哪个dto
查询出来后每次都会給goods中的goodsId进行赋值
用GoodsDTO.java 来承载多表关联查询的结果

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private String categoryName;
    private String test;
    Getter+Setter
}

Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
}
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="categoryName" column="category_name"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private Category category = new Category();
    private String test;
    Getter+Setter...
}

Category.java
public class Category { //标准的Java Bean 换DTO的  private Category category = new Category();
    private Integer categoryId;
    private String categoryName;
    private Integer parentId;
    private Integer categoryLevel;
    private Integer categoryOrder;
    Getter+Setter...
}
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis数据插入操作

数据库事务
  • 数据库事务是保证数据操作完整性的基础

客户端[Java] → 事务日志[增删改查数据] → 向MySQL写入commit → 数据表作为更新数据 【若执行roll back后事务日志和数据表的数据都会被清除】

MyBatis写操作包含三种
  • 插入< insert >
goods.xml 新增操作
 <!--flushCache="true"在sql执行后强制清空缓存-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods"flushCache="true">
 INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
 VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
  <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
    select last_insert_id()
  </selectKey>
</insert>
MyBatisTestor.java
@Test
    public void selectInsert() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = session.insert("goods.insert", goods);
            session.commit();
            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if (session != null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
  • 更新 < update>
    不推荐goods.set…来插入,而是推荐使用获取到原始的商品信息Goods goods = session.selectOne("goods.selectById", 739);再在原始信息上做出调整和更新。对数据影响最小
goods.xml
 <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>
MyBatisTestor.java
 @Test
    public void testUpdate() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 739);//得到指定编号的对象
            goods.setTitle("更新测试商品");
            int num = session.update("goods.update", goods);
            session.commit();
        }catch (Exception e){
            if (session!=null)
                session.rollback();
        }
    }
  • 删除 < delete >
    大多数删除操作都是根据主键来运行的
good.xml
<delete id="delete" parameterType="integer">
    delete from t_goods where goods_id = #{value}
</delete>
MyBatisTestor.java
 @Test
    public void Delete(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            int num = session.delete("goods.delete", 739);
            session.commit();
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

SelectKeyUserGeneratedKeys的区别

SelectKey属性用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  INSERT INTO SQL语句
============================获取主键值===============================(↑案例)
 <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
   select last_insert_id()
 </selectKey>
</insert>

★ selectKey标签需要明确编写获取最新主键的SQL语句  获取主键 ★
★ selectKey适用于所有的关系型数据库 ★
★ selectKey标签是通用方案,适用于所有数据库,但编写麻烦 ★
UserGeneratedKeys属性用法
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
   userGeneratedKeys="true"
   keyProperty="goodsId" <!--主键值-->
   keyColumn="goods_id"> <!--字段名-->
  INSERT INTO SQL语句
</insert>
★ useGeneratedKeys属性会自动根据驱动生成对应SQL语句 自动获取主键 ★
★ useGeneratedKeys只支持"自增主键"类型的数据库 ★
★ useGeneratedKeys属性只支持"自增主键"数据库,使用简单
在Oracle中selectKey的用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  InSERT INTO SQL语句
  <selectKey resultType="Integer" order=“BEFORE” keyProperty="goodsId">
      SELECT seq_goods.nextval as id from dual  
  </selectKey>
</insert>

SQL注入攻击[缺少转义操作]

  • SQL注入是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式
SQLd代码:
"select * from a where name ='" + name + " '";

正常情况下:
name:张三 → select * from a where name = '张三';

SQL注入攻击:
name:' or 1=1 or 1='
select * from a whrer name = " or 1=1 or 1="
MyBatis两种传值方式
  • ${}文本替换(原文传值)(产生的sql语句绝不能是外界输入的), 未经过任何处理对SQL文本替换 {根据前台输入的条件不同来选择不同的字段排序 没有对输入的数据进行预编译处理 导致输入的内容变成了sql的一部分}

    ${order}
    param.put("order"," order by title desc");

    用${}原传值:select * from t_goods where title = ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); 成为sql的一部分

  • #{}预编译传值, 使用预编译传值可以预防SQL注入

    用#{}预编译:select * from t_goods where title = “ ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); #输入的会变成字符串

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
  select * from t_goods where title = ${title}
</select>
MyBatisTestor.java
 @Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            pparam.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

使用上传照片

MyBatis进阶教程

MyBatis日志管理

什么是日志
  • 日志文件是用于记录系统操作事件的记录文件或文件集合
  • 日志保存历史数据, 是诊断问题以及理解系列活动的重要依据
ch.qos.logback logback-classic 1.2.3

上传照片

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>
MyBatisTestor.java
@Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
   }
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
<!--            23:39:16.761 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.-->
        </encoder>
    </appender>
    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
<!--生产环境最低级别设置info以上 开发环境最低级别设置debug以上方便调试-->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

MyBatis动态SQL

动态SQL
  • 动态SQL是指根据参数数据动态组织SQL的技术
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com...Goods">
    select * from t_goods
    where     <!--动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
</select>
goods.xml
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
<where></where>  where标签会动态的对子sql进行判断

MyBatis二级缓存 (可共享对象)

sql语句第一次查询 sql存储在硬盘上
优化:第一次查询的时候放在某个内存 再次访问就很快 (缓存)

  • 一级缓存默认开启,缓存范围SqlSession会话
  • 二级缓存手动开启,属于范围Mapper Namespace

二级缓存运行规则

  • 二级开启后默认所有查询操作均使用缓存
  • 写操作commit提交时对该namespace缓存强制清空
  • 配置useCache=false可以不用缓存
  • 配置flushCache=true代表强制清空缓存
@Test
public void testLv1Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            session.commit();//commit提交时对该namespace缓存强制清空
            Goods goods1 = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode() + ":" + goods1.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

Goods goods = session.selectOne(“goods.selectById”, 1603);
session.commit();//commit提交时对该namespace缓存强制清空
缓存和语句距离太短 资源浪费 使用率不高哦

<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>

运行结果只出现一条sql语句 结果的goods.hasCode的结果来自同一块内存区域
MyBatisTestor.java
 @Test
    public void testLv2Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }

        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

08:59:50.304 [main] DEBUG goods - Cache Hit Ratio [goods]: 0.5
缓存命中率越高 证明优化越好
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <!--开启了二级缓存
        eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
        1. LRU - 最近最久未使用:移除最长随时间不被使用的对象{893}
           LFU - 最近时间内方位最少的移除{1}
        【LRU + LFU 增强缓存速度】
        01 02 03 04 .. 0512
        14 99 83 1        893
        2. FIFO - 先进先出:按对象进入缓存的顺序来移除它们
        3. SOFT - 软引用:移除基于垃圾收集器状态和软引用规则的对象
        4. WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象
        flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒=10分钟
        size 缓存存储上限,用于保存对象或集合(1个结婚算1个对象)的数据上限
        readOnly 设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
                 设置为false,代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
    -->
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
    <!--useCache="false"代表查询结果不放入缓存-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
    <select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where goods_id = #{value}
    </select>

    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
<!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
        <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
        select last_insert_id()
        </selectKey>
    </insert>

    <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>

    <delete id="delete" parameterType="integer">
        delete from t_goods where goods_id = #{value}
    </delete>

    <select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>

    <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
</mapper>
一级缓存被默认开启的随着sql开 随着sql关

复习<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/> 当中的各种

<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">

MyBatis多表级联查询 (通过一个对象获得另外一个对象)

一个比较有多个学生 而 一个学生在同一时间只能隶属于一个班级

实体关系分析

商品和详情对象关联查询 [商品是1 详情是多 详情那方要持有商品的主键]

ManyToOne对象关联查询 [collection]

mybatis-config.xml 最底下加一层
<mappers>
   <mapper resource="mappers/goods.xml"/>
   <mapper resource="mappers/goods_detail.xml"/>
</mappers>


good.xml
 <!--
        resultMap可以用于说明一对多或者多对一的映射逻辑
        id 是 resultMap属性引用标志
        type 指向One的实体(Goods)
    -->
    <resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
        <!-- 映射goods对象的主键到goods_id字段 -->
        <id column="goods_id" property="goodsId"></id>
        <!--
            collection的含义是,在
            select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
            并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
            将得到的"商品详情"集合赋值给goodsDetails List对象.
        -->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
                    column="goods_id"/>
    </resultMap>
    <select id="selectOneToMany" resultMap="rmGoods1">
        select * from t_goods limit 0,10
    </select>


goods_details.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>
</mapper>
Goods.java 再加一个
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    private List<GoodsDetail> goodsDetails;
    Getter+Setter
}
MaBatisTestor.java 
/**
     * 测试多对一对象关联映射
     */
    @Test
    public void testOneToMany() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectOneToMany");
            for(Goods goods:list) {
                System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

ManyToOne对象关联查询 [association]

商品和详情对象关联查询
goods_detail.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>

    <resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
        <id column="gd_id" property="gdId"/>
        <!--字段映射-->
        <result column="goods_id" property="goodsId"/>
        <!--从多的一方关联单的一方     在goods.xml中的goods空间 根据查询结果id带入到这个语句赋值到goods语句-->
        <association property="goods" select="goods.selectById" column="goods_id"></association>
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 0,20
    </select>
</mapper>
MyBatisTestor.java
 @Test
    public void testManyToOne() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
            for(GoodsDetail gd:list) {
                System.out.println(gd.getGdPicUrl() + ":" + gd.getGoods().getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
GoodsDetail.java
public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;
    Getter + Setter
}

分页查询的麻烦事

  • 当前页数据查询 - select * from tab limit 0,10
  • 总记录数查询 - select count(*) from tab
  • 程序计算总页数、上一页页码、下一页页码
分页插件PageHelper

PageHelper使用流程

  • maven引入PageHelper与jsqlparser
  • mybatis-config.xml增加Plugin配置
  • 代码中使用PageHelper.startPage()自动分页
pom.xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId> <!--最核心处理的sql语句-->
    <version>2.0</version>
</dependency>


goods.xml
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where current_price &lt; 1000
</select>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--启动Pagehelper分页插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
            <!--分页合理化-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
        <mapper resource="mappers/goods_detail.xml"/>
    </mappers>
</configuration>
MyBatisTestor.java
@Test
    public void testSelectPage() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            /*startPage方法自动将下一次查询进行分页*/
            PageHelper.startPage(2,10);
            Page<Goods> page = (Page)session.selectList("goods.selectPage");
            System.out.println("总页数:" + page.getPages());
            System.out.println("总记录数:" + page.getTotal());
            System.out.println("开始行号:" + page.getStartRow());
            System.out.println("结束行号:" + page.getEndRow());
            System.out.println("当前页码:" + page.getPageNum());
            List<Goods> data = page.getResult();//当前页数据
            for (Goods g : data) {
                System.out.println(g.getTitle());
            }
            System.out.println("");
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

不同数据库分页的实现原理 [面试/笔试]

MySQL分页 select * from table limit 10,20; 起始行号 向后取多少值
Oracle分页(三层嵌套)

Oracle分页(三层嵌套)
select t3.* from(
    select t2.*,rownum as row_num from(
        select * from table order by id asc
    ) t2 where rownum <= 20
)t3
where t2.row_num>11

SQL Server 2000

select top 3 * from table
where
  id not in
  (select top 15 id from table)

SQL Server 2012+

select * from table order by id 
   offset 4 rows fetch next 5 rows only

MyBatis配置C3P0连接池

pom.xml
<dependency>
     <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.4</version>
</dependency>
mybatis-config.xml
<environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.mybatis.datasource.C3P0DataSourceFactory">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="user" value="root"/>
                <property name="password" value="root"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
                <property name="minPoolSize" value="5"/>
                <!--...-->
            </dataSource>
        </environment>
C3P0DataSourceFactory.java
package com.imooc.mybatis.datasource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * C3P0与MyBatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory{
    public C3P0DataSourceFactory(){
        this.dataSource = new ComboPooledDataSource();
    }
}

MyBatis批处理 [利用集合保存批处理数据] [海量数据导入]

goods.xml
<!--    INSERT INTO table-->
<!--    VALUES("a","a1","a2"),("b","b1","b2"),(...)-->
<insert id="batchInsert" parameterType="java.util.List">
     INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
     VALUES
    <foreach collection="list" item="item" index="index" separator=",">
      (#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
    </foreach>
<!--批量插入数据的局限:1.无法获得插入数据的id  2.批量生成的SQL太长,可能会被服务器拒绝(可以分段2次for循环)-->
</insert>
MyBatisTestor.java
/**
     * 批量插入测试
     * @throws Exception
     */
    @Test
    public void testBatchInsert() throws Exception {
        SqlSession session = null;
        try {
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数

                list.add(goods);
            }
            session.insert("goods.batchInsert", list);
            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (session != null) {
                session.rollback();//回滚事务
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

====================================================================
 /**
     * 10000次数据插入对比测试用例
     * @throws Exception
     */
    @Test
    public void testInsert1() throws Exception {
        SqlSession session = null;
        try{
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for(int i = 0 ; i < 10000 ; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数
                //每循环一次 插入一次
                session.insert("goods.insert" , goods);
            }

            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et-st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis常用注解 (注解适合小型敏捷项目 XML适合大型协作项目)

注解 对应XML 说明
@Insert < insert > 更新SQL
@Update < update > 更新SQL
@Delete < delete > 删除SQL
@Select < select > 查询SQL
@Param 参数映射
@Results < resultMap > 结果映射
@Result < id > < result > 字段映射
把XML中的写入代码中
dao.GoodsDAO.java
package com.imooc.mybatis.dao;

import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface GoodsDAO {
    @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}")
    public List<Goods> selectByPriceRange(@Param("min") Float min, @Param("max") Float max, @Param("limit") Integer limit);

    @Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    //<selectKey> befor是true则在这之前执行 keyProperty主键 resultType返回主键类型
    @SelectKey(statement = "select last_insert_id()" , before = false , keyProperty = "goodsId" , resultType = Integer.class)
    public int insert(Goods goods);

    @Select({"select * from t_goods"})
    //<resultMap>
    @Results({ //select实行完以下结果 按照以下规则并赋值給GoodsDTO对象
            //<id>
            @Result(column = "goods_id", property = "goodsId", id = true),
            //<result>
            @Result(column = "title", property = "title"),
            @Result(column = "current_price", property = "currentPrice"),
    })
    public List<GoodsDTO> selectAll();

}
dto.GoodsDTO
package com.imooc.mybatis.dto;

public class GoodsDTO {
    private Integer goodsId;//商品编号
    private String title;//标题
    private Float currentPrice;//当前价格

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }
}
entity.Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter + Setter
}
resources.logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>
resources.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- goods_id ==> goodsId 驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置默认指向的数据库-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        <mapper class="com.imooc.mybatis.dao.GoodsDAO"/>-->
        <package name="com.imooc.mybatis.dao"/>
    </mappers>
</configuration>
test.java.com.imooc.mybatis.MyBatisTestor
package com.imooc.mybatis;

import com.imooc.mybatis.dao.GoodsDAO;
import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {

    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); //包含注解的接口
            List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

    /**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = goodsDAO.insert(goods);
            session.commit();//提交事务数据
            System.out.println(goods.getGoodsId()); //最新的数据回填給goodsId
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            List<GoodsDTO> list = goodsDAO.selectAll();
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.imooc</groupId>
    <artifactId>mybatis-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
</project>
阅读全文

Maven,工厂模式,反射模式,Lambda表达式,stream流式处理

2023/10/26

Maven构建工具

Maven介绍
  • Maven是项目管理工具,对软件项目提供构建与依赖管理
  • Maven是Apache下的Java开源项目
  • Maven为Java项目提供了统一的管理方式,已经成为业界标准
Maven核心特性
  • 项目设置遵循统一的规则,保证不同开发环境的兼容性
  • 强大的依赖管理,项目依赖组件自动下载、自动更新
  • 可扩展的插件机制,使用简单,功能丰富
Maven的坐标
  • GroupId:机构或者团体的英文,采用”逆向域名”形式书写
  • ArtifactId:项目名称,说明其用途,例如:cms、oa…
  • Version:版本号,一般采用”版本+单词”形式,例如:1.0.0.RELEASE

Maven项目标准结构

目录 用途
${basedir} 根目录,用于保存pom.xml
${basedir}/src/main/java Java源代码目录
${basedir}/src/main/resources 资源目录,保存配置文件、静态图片等
${basedir}/src/test/java 测试类的源代码
${basedir}/src/test/resources 测试时需要使用的资源文件
${basedir}/target 项目输出的目录,用于存储jar、war文件
${basedir}/target/class 字节码(.class)的编译输出目录
${basedir}/pom.xml 项目(Project)对象(Object)模型(Model)文件

Maven依赖管理

  • Maven利用dependency(依赖) 自动下载、管理第三方Jar
  • 在pom.xml文件中配置项目依赖的第三方组件
  • maven自动将依赖从远程仓库下载至本地仓库,并在工程中引用
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

Maven Central Repository Search可以搜索maven

pom.xml
<dependencies>
    <dependency>
          <groupId>com.belerweb</groupId>
          <artifactId>pinyin4j</artifactId>
          <version>2.5.1</version>
    </dependency>
</dependencies>
PinyinTestor.java
import net.sourceforge.pinyin4j.PinyinHelper;

import java.util.Scanner;

public class PinyinTestor {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String[] pingyin = PinyinHelper.toHanyuPinyinStringArray(str.charAt(0)); //将输入的第一个数据变成字符串数组
        for (String py : pingyin){
            System.out.println(py);
        }
    }
}

本地仓库与中央仓库

maven 在项目启动的时候会对 pom.xml 进行加载 之后会在本地仓库 .m2\repository 去查找依赖文件(jar包) 如果查不到或不存在 就会去中央仓库下载(repo.maven.apache.org)到本地仓库

项目打包

  • Maven可将Java项目打包为jar、war
  • Maven项目打包是通过Plugins(插件)技术实现
  • Maven输出jar包插件:maven-assembly-plugin
//阿里云镜像下载地址
<repositories>
  <repository>
      <!-- 创建私服的地址 -->
    <id>aliyun</id>
    <name>aliyun</name>
    <url>https://maven.aliyun.com/repository/public</url>
  </repository>
</repositories>

Maven构建Web工程

创建Maven-Project

Group Id: maven-first
Artifacr Id: maven
Version: 1.0.0-RELEASE
Packaging: jar

IntelliJ IDEA创建maven web项目(IDEA新手适用)_idea maven创建web项目-CSDN博客
IDEA2022版本创建maven web项目(两种方式)最全图文教学_idea创建maven项目没有webapp-CSDN博客
IDEA中创建Maven Web项目的两种方法_idea maven创建web项目-CSDN博客
Project Structure → Modules → +增加Web

web application exploded:这个是以文件夹形式发布项目,发布项目时就会自动生成文件夹在指定的output directory;
web application archive:就是war包形式,将项目打成一个war包在指定位置

Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)_tomcat 404_ISAS的博客-CSDN博客
Windows下如何查看某个端口被谁占用 | 菜鸟教程 (runoob.com)
(.iml的问题)IDEA中用Maven创建web项目部署运行时页面报错404解决方法_maven web项目404-CSDN博客

Web应用打包

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Web_one</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar\war</packaging>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
        </plugins>
    </build>

</project>

打包成war\jar后可以通过tomcat中的临时启动服务器来运行

Maven常用命令

命令 用途
mvn archetype:generate 创建Maven工程结构
mvn compile 编译源代码 .class
mvn test 执行测试用例
mvn clean 清除产生的项目
mvn package 项目打包
mvn install 安装至本地仓库

修改本地仓库地址

工厂模式 [运用于真实项目]

设计模式
  • 设计模式是前辈总结的设计经验
  • 设计模式的目标是代码更容易理解,更容易维护
  • 通过设计模式可以让代码更加可靠
设计模式的分类
  • 创建型模式 [帮助我们如何更精巧的创建对象]
  • 结构型模式 [在软件结构上通过重构\抽象 让软件结构变得更有条理]
  • 行为型模式 [现实中的场景对软件的设计和优化]
工厂模式
  • 工厂模式用于隐藏创建对象的细节
  • 工厂模式核心:工厂类(Factory)
  • 工厂模式可以细分为简单工厂、工厂方法与抽象方法

简单工厂图

项目应用-i18n国际化 (软件分工更明确 软件耦合降低)

不同国家显示页面的语言不同
抽象一个接口!!

反射

反射Reflect
  • 反射式在运行时动态访问类与对象的技术 [写死的代码不灵活]
  • 反射是JDK1.2版本后的高级特性,隶属于java.lang.reflect
  • 大多数Java框架都是基于反射实现参数配置、动态注入等特性
初始反射技术
未运用反射技术
 public static void case1(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next();
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        if(op.equals("Addition")){
            mathOperation = new Addition();
        }else if(op.equals("Subtraction")) {
            mathOperation = new Subtraction();
        }else if(op.equals("Multiplication")){
            mathOperation = new Multiplication();
        }else{
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result);
    }

======================================================
运用反射技术
 public static void case2(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next(); ****
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        try {
            mathOperation = (MathOperation) Class.forName("com.imooc.reflect." + op).newInstance(); //class.forName加载指定的类 实例化对象 运行时动态决定op创建哪些对象 访问哪些属性****
        }catch(Exception e){
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result); 
    }

反射的核心类

Class类

Class是JVM中代表”类和接口”的类
Class对象具体包含了某个特定类的结构信息
通过Class对象可获取对应类的构造方法/方法/成员变量

Class核心方法
方法 用途
Class.forName() [传入完整类名包括包] 静态方法,用于获取指定Class对象
classObj.newInstance() 通过默认构造方法创建新的对象
classObj.getConstructor() 获得指定的public修饰构造方法Constructor对象
classObj.getMethod() 获取指定的public修饰方法Method对象
classObj.getField() 获取指定的public修饰成员变量Field对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}
ClassSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

public class ClassSample {
    public static void main(String[] args) {
        try {
            //Class.forName()方法将指定的类加载到jvm,并返回对应Class对象
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            System.out.println("Employee已被加载到jvm");
            //newInstance通过默认构造方法创建新的对象
            Employee emp = (Employee)employeeClass.newInstance();
            System.out.println(emp);
        } catch (ClassNotFoundException e) {
            //类名与类路径书写错误时抛出"类无法找到"异常
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
Constructor构造方法类
  • Constructor类是对Java类中的构造方法的抽象
  • Contructor对象包括了具体类的某个具体构造方法的声明
  • 通过Constructor对象调用带参构造方法创建对象
方法 用途
classObj.getConstructor() 获取指定public修饰的构造方法对象
constructorObj.newInstance() 通过对应的构造方法创建对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }
}
ConstructorSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{ //得到对应的class对象
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            System.out.println(employee);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
            //类名与类路径书写错误时抛出"类无法找到"异常
        } catch (NoSuchMethodException e) {
            //没有找到与之对应格式的写法
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            //当被调用的方法的内部抛出了异常而没有被捕获时
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
}
Method类
  • Method对象指代某个类中的方法的描述
  • Method对象使用classObj.getMethod()方法获取
  • 通过Method对象调用指定对象的对应方法

Method核心方法

方法 用途
classObj.getMethod() 获取指定public修饰的方法对象
methodObj.invoke() 调用指定对象的对应方法
Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
MethodSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Method updateSalaryMethod = employeeClass.getMethod("updateSalary", new Class[]{ //传入参数
               Float.class
            });
            Employee employee1 = (Employee)updateSalaryMethod.invoke(employee, new Object[]{1000f}); //若有返回值 要强制转换
            System.out.println(employee1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
Field成员变量类
  • Field对应某个具体类中的成员变量的声明
  • Field对象使用**classObj.getField()**方法获取
  • 通过Field对象可为某对象成员变量赋值/取值

Field类核心方法

方法 用途
classObj.getField() 获取指定publicc修饰的成员变量对象
fieldObj.set() 为某对象指定成员变量赋值
fieldObj.get() 获取某对象指定成员变量数值

快速添加包裹try catch → 框上要包裹的 点Code → surround with
get()/set()都是(在entify中)public共有方法

Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    public String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
FieldSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field enameField = employeeClass.getField("ename");
            enameField.set(employee,"李雷");
            String ename = (String) enameField.get(employee);
            System.out.println("ename:" + ename);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

getDeclared系列方法

  • getDeclaredConstructor(s) | Method(s) | Field(s) 获取对应对象
  • getConstructor(s) | Method(s) | Field(s) 只能获取public对象
  • 访问非作用域内构造方法、方法、成员变量,会抛出异常

public可以直接获取 private只能通过get…获取

Employee.java + getDeclaredSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getDeclaredSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field[] fields = employeeClass.getDeclaredFields();
            for (Field field : fields){
//                System.out.println(field.getName());
                if (field.getModifiers() == 1) {//成员变量修饰符 public修饰
                    Object val = field.get(employee);
                    System.out.println(field.getName() + ":" + val);
                } else if (field.getModifiers() == 2) { //private修饰
                    String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//(0,1)是截取字符串 第一个大写字母
                    Method getMethod = employeeClass.getMethod(methodName);
                    Object ret = getMethod.invoke(employee);
                    System.out.println(field.getName() + ":" + ret);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

反射在项目中的应用

反射最重要的就是可以在运行时

反射的应用网站

对原始程序无需任何调整,只需要把对应的接口进行实现,放到线上服务器,再调整配置文件。

Zhcn.java
package com.imooc.i18n;

public class Zhcn implements I18N{
    @Override
    public String say() {
        return "生命不息奋斗不止";
    }
}
=========================================================
En.java
package com.imooc.i18n;

public class En implements I18N{
    @Override
    public String say() {
        return "Case to the struggle and cease to the life";
    }
}
========================================================
接口I18N.java
package com.imooc.i18n;

public interface I18N {
    public String say();
}
package com.imooc.i18n;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Properties;

public class Application {
    public static void say(){
        Properties properties = new Properties(); //加载指定的配置文件
        String configPath = Application.class.getResource("/config.properties").getPath();
        try {
            configPath = new URLDecoder().decode(configPath,"UTF-8");//路径中的空格默认得到url编码所以要转换一下
            properties.load(new FileInputStream(configPath)); //内容来源于文件 文件输入流
            String language = properties.getProperty("language");
            I18N i18n = (I18N)Class.forName(language).newInstance();
            System.out.println(i18n.say());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Application.say();
    }
}
language=com.imooc.i18n.En
或
language=com.imooc.i18n.Zhcn

Lambda表达式

  • JDK8(1.8以上)开始支持Lambda表达式,用来让程序编写更优雅
  • 利用Lambda可以更简洁的实现匿名内部类函数声明与调用
  • 基于Lambda提供stream流式处理极大简化对集合的操作
传统代码
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//实现集合排序
Collections.sort(names,new Comparator<String>(){
  @Override
  public int compare(String a,String b){
      return b.compareTo(a);
  }  
});
使用Lambda表达式
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//通过lambda表达式简化匿名类的编写
Collections.sort(names,(a,b) -> b.compareTo(a));

Lambda表达式语法 [代码脚手架]

(参数列表) —>实现语句
[使用逗号分割参数,参数类型可省略,单参数括号可省略]
[单行直接写 多行用{}包括]

MathOperation.java
public interface MathOperation {
    //四则运算接口
    public Float operate(Integer a, Integer b);
}
LambdaSample.java
public class LambdaSample {
    public static void main(String[] args) {
        //标准Lambda使用方法
        //约束条件:Lambda表达式只能实现有且只有一个抽象方法的接口,Java称为"函数式接口"
        //1.标注使用方式
        MathOperation addition = (Integer a, Integer b) -> {
            System.out.println("加法运算");
            return a+b+0f; //定义的接口是Float
        };
        System.out.println(addition.operate(5, 5));

        //2.lambda允许忽略参数类型
        MathOperation substration = (a,b) -> {
            return a-b+0f;
        };
        System.out.println(substration.operate(5, 3));

        //3.单行实现代码可以省略大括号和return
        MathOperation multiplication = (a,b) -> a*b+0f;
        System.out.println(multiplication.operate(3, 5));
    }
}

函数式编程

  • 函数式编程是基于函数式接口并使用lambda表达的编程方式
  • 函数式编程理念是将代码作为可重用数据带入到程序运行中
  • 函数式编程强调”你想做什么“,而不是”你想怎么做

函数式接口

  • 函数式接口是有且只有一个抽象方法的接口
  • Java中拥有大量函数式接口,如java.lang.Runnable
  • JDK8后提供了一系列新的函数式接口,位于java.util.function

函数式接口Perdicate

  • Perdicate是新增的函数式接口,位于java.util.function
  • Perdicate用于测试传入的数据是否满足判断要求
  • Perdicate接口需要实现test()方法进行逻辑判断

lambda表达式来实现predicate的验证
将已有的代码变成可重复使用的资源放入程序中

PredicateSample.java
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/*
    理解函数式编程
    Perdicate函数式接口的使用方法
 */
public class PredicateSample {
    public static void main(String[] args) {
        Predicate <Integer> predicate = n->n>4; //隐藏着return
        boolean result = predicate.test(10);
        System.out.println(result);
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        filter(list,n->n%2==1); //传入函数式接口的实现lambda 取所有奇数
        filter(list,n->n%2==0); //取所有偶数
        filter(list,n->n>5 && n%2==0); //取所有大于5的偶数
    }
    public static void filter(List<Integer> list, Predicate<Integer> predicate){
        for (Integer num:list){
            if (predicate.test(num)){
                System.out.println(num + " ");
            }
        }
    }
}

各种函数接口

JDK8常用函数式接口
consumer函数接口
接口 用途
Consumer< T > 对应有一个输入参数无输出的功能代码
Function< T,R > 对应有一个输入参数且需要返回参数的功能代码
Predicate< T > 用于条件判断,固定返回布尔值
ConsumerSample.java
import java.util.function.Consumer;

/*
    Consumer接口的使用
 */
public class ConsumerSample {
    public static void main(String[] args) {
        output(s-> System.out.println("向控制台打印:" + s));
        //字符串作为网络数据包向某个网站发送
        output(s->{
            System.out.println("向XXX网络发送数据包:" + s);
        });
    }
    public static void output(Consumer<String> consumer){
        String text = "苦其心志,劳其筋骨";
        consumer.accept(text);
    }
}
Function接口
FunctionSample.java
import java.util.Random;
import java.util.function.Function;

/*
    利用Function函数式接口生成定长随机字符串[加密解密会用到]
 */
public class FunctionSample {
    public static void main(String[] args) {
        Function<Integer,String> randomStringFunction = l->{
            String chars = "abcdefghijklmnopqrstuvxwyz0123456789";
            StringBuffer stringBuffer = new StringBuffer();
            Random random = new Random();
            for (int i = 0; i < l; i++) {
                int position = random.nextInt(chars.length());
                stringBuffer.append(chars.charAt(position));//按指定位置将字符提取并追加
            }
            return stringBuffer.toString();
        };
        String randowmString = randomStringFunction.apply(16);//生成16位长的字符串
        System.out.println(randowmString);
    }
}
@functionalInterface注解
MathOperation.java
@FunctionalInterface //通知编译器这是函数式接口,进行抽象方法检查
public interface MathOperation {
    //四则运算接口

    public Float operate(Integer a, Integer b);
}

函数式编程与面向对象编程比较

面向对象编程 函数式编程
设计思路 面向对象 面向过程
开发侧重 侧重过程,重分析,重设计 侧重结果,快速实现
可读性 结构复杂,相对较差 更适合人眼阅读,可读性更好
代码量
并发问题 设计不当,会出现线程安全问题 不会出现线程安全问题
健壮性
使用场景 中大型项目,多人协作工程 小型应用,要求快速实现

Stream流式处理

  • Stream流式处理式建立在Lambda基础上的多数据处理技术
  • Stream对集合数据处理进行高度抽象,极大简化代码量
  • Stream可对集合进行迭代,去重,筛选,排序,聚合等一系列处理

Stream示例

//获取List集合中最大的偶数
Optional<Integer> op = Arrays.asList(1,2,3,4,5,6).stream()
.filter(x->x%2==0) //处理完得到一个只包含偶数的list流数据
.sorted((a,b)->b-a) //大的在前面 小的在后面
.findFirst(); //获取最大的数据
System.out.println(op.get());

Stream常用方法

接口 用途
forEach 循环遍历
map map方法用于映射每个元素到对应的结果
filter filter方法用于通知设置的条件过滤出元素
limit limit方法用于获取指定数量的流
sorted sorted方法用于对流进行排序
Collectors Collectors类实现将流转换成集合和聚合元素
StreamGenerator.java
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/*
Stream流对象的五种创建方式
 */
public class StreamGenerator {
    //1.基于数组进行创建
    @Test
    public void generator1(){
        String[] arr = {"Lily","Andy","Jackson","Smith"};
        Stream<String> stream = Stream.of(arr);
        stream.forEach(s -> System.out.println(s)); //forEach中使用Lambda表达式
    }

    //2.基于集合进行创建
    @Test
    public void generator2(){
        List<String> list = new ArrayList<>();
        list.add("Lily");
        list.add("Andy");
        list.add("Jackson");
        list.add("Smith");
        Stream<String> stream = list.stream(); //利用集合获取stream
        stream.forEach(s -> System.out.println(s));
    }

    //3.利用generate方法创建无限长度流
    @Test
    public void generator3(){
        Stream<Integer> stream = Stream.generate(() -> new Random().nextInt(100000));//Supplier<T> s 创建新对象
        stream.limit(10).forEach(i -> System.out.println(i)); //limit限制长度
    }

    //4.基于迭代器创建流
    @Test
    public void generator4(){
        Stream<Integer> stream = Stream.iterate(1, n -> n + 1);//无限长度自增
        stream.limit(100).forEach(i -> System.out.println(i));
    }

    //5.基于字符序列创建流
    @Test
    public void genetator5(){
        String str = "abcdefg我";
        IntStream stream = str.chars();
        stream.forEach(c -> System.out.println((char)c));
    }
}
StreamMethod.java
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMethod {
    @Test //提取集合中所有偶数并求和
    public void case1(){
        List<String> list = Arrays.asList("1", "2", "3", "4", "5");
        int sum = list.stream() //获取stream对象
                .mapToInt(s -> Integer.parseInt(s)) //对每个元素字符串转为整数
                .filter(n -> n%2==0) //filter对流数据进行过滤
                .sum();//求和
        System.out.println(sum);
    }

    @Test //所有名字首字母大写
    public void case2(){
        List<String> list = Arrays.asList("lily","smith","jackson");
        List newList = list.stream()
                .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) //首字母大写转换
//                .forEach(s -> System.out.println(s));
                .collect(Collectors.toList()); //collect对流数据进行收集,生成新的List/Set(将重复数据自动清除)
        System.out.println(newList);
    }

    @Test //将所有奇数从大到小进行排序,且不允许出现重复
    public void case3(){
        List<Integer> list = Arrays.asList(1, 60, 38, 21, 51, 60, 51, 73);
        List newList = list.stream().distinct() //去除重复的流数据
                .filter(n -> n%2==1)
                .sorted((a,b) -> b-a) //从大到小的数据排列
                .collect(Collectors.toList());
        System.out.println(newList);
    }
}
阅读全文

java数据库开发(JDBC)

2023/10/23

IDEA窗口快捷键

快捷键 描述
Ctrl+Alt+S Settings面板
Ctrl+Shift+F/R 当前Project中全局查找/替换
Ctrl+Shif+N 文件查找面板
Alt+Insert 快速生成面板
Ctrl+Shift+A Find Action模糊查询快速定位

代码快捷键

快捷键 描述
Ctrl+←→ 上一个/下一个单词
Ctrl+Shift+Enter 自动完成
Alt+Enter 智能提示
Ctrl+Alt+L 格式化代码
Ctrl+(Shift)+/ 行注释/块注释
Ctrl+Alt+Shift+J 列操作
Shift+F6 重命名(当前选择变量修改)
Ctrl+W 选中单词

代码快速定位

快捷键 描述
Ctrl+(Shift)+E 最近访问(编辑)的文件列表
Ctrl+Shift+1~9 创建书签
Shift+F11 查看书签
Ctrl+1~9 快速切换书签
Alt+←→ 切换书签
Template使用

Live Templates可以添加常用快捷字母作为快捷代码

右方 + “custom”
Abbreviation:al”
Description:Create ArrayList”
Template text:List< String >list = new ArrayList();”
Template text:List< $VAR1$>$VAR2$ = new ArrayList();”
点击define设置全部

运行与打包

快捷键 描述
Shift+F9 调试
Shift+F10 运行
F8 单步运行
F9 恢复运行至下一个端点
Shift+Ctrl+F8 查看所有端点
Jar包核心配置文件设置(加载响应的Class)

将编译的类导入jar包

‘……’ compile output
Project Structure → Artifacts → Outpub Layout

设置jar包的入口类 → Create Manifest… → Main Class(Jar包加载相应目录)

生成jar包

上述操作完毕后 点BuildBuild Artifacts…

IDEA快速开发Web应用

New Project → Java Enterprise → SDK1.8 = Java EE7

改变Tomcat启动时自动弹出的地址

Run/Debug Configurations → Deployment → 下方的 Application context

Project Structure

Artifacts 中默认存在 javaweb: war exploded 代表用文件夹的方式与Tomcat联动
点Add → Web Application: Archive打包 右侧是待添加 [文件成功发布到jar包中 ‘javaweb’ compile output] 右侧的Web facet resources 是 jsp, html等静态资源双击放到左边[‘javaweb’module: ‘Web’ facet resources] 至此javaweb.war包就包含了所有文件。上述操作完毕后 点BuildBuild Artifacts… build!!之后若在实际运行的时候放在D:\apache-tomcat-8.5.93\webapps内 之后启动tomcat[D:\apache-tomcat-8.5.93\bin\startup.bat]就可以显示结果

JDBC(Java DataBase Connectivity)快速入门

JDBC作用,在java程序中与关系型数据库进行交互

JDBC优点
  • 统一的API,提供一致的开发过程
  • 易于学习,容易上手,代码结构稳定
  • 功能强大,执行效率高,可处理海量数据

开发流程

1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Satement对象
4.遍历查询结果
5.关闭连接.释放资源

Class.forName的作用

  • Class.forName用于加载指定的JDBC驱动类
  • Class.forName本质是通知JDBC注册这个驱动类
  • 驱动由数据库厂商自行开发,俩厂家不同链接祖父串了,字符串也不同

创建数据库连接代码

String dbDriver = "com.mysql.cj.jdbc.Driver"; //JDBC驱动类
String dbURL = "jdbc:mysql://localhost:3306/imooc"; //连接字符串
String dbUsername =  "root";
String dbPassword = "123456";
//1.加载并初始化JDBC驱动
Class.forName(dbDriver);
//2.创建数据库连接
Connection connection = DriverManager.getConnection(dbURL,dbUsername,dbPassword);
StandardJDBCSample.java
package com.example.imoocjdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class StandardJDBCSample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            //1.加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver"); //加载指定的类
            //2.创建数据库连接
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&&allowPublicKeyRetrieval=true",
                    "root", "root"
            ); //程序和数据库的网络通信桥梁
            //3.创建Satement对象 ResultSet结果集
            Statement stmt = conn.createStatement(); //一条或多条sql语句
            ResultSet rs = stmt.executeQuery("SELECT * FROM employee WHERE dname='研发部'");
            //4.遍历查询结果
            while (rs.next()) {
                Integer eno = rs.getInt(1); //把当前行指定未知的提取 eno
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (conn != null && conn.isClosed() == false) {
                    //5.关闭连接.释放资源
                    conn.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

JDBC驱动的秘密

DriverManager
  • DriverManager用于注册/管理JDBC驱动程序
  • DriverManager.getConnection(连接字符串,用户名,密码)
  • 返回值Connection对象,对应数据库的物理网络连接
Connection对象
  • Connection对象用于JDBC与数据库的网络通信对象
  • java.sql.Connection是一个接口,具体由驱动厂商实现
  • 所有数据库的操作都建立在Connection上

MySQL连接字符串

  • 格式: jdbc:mysql://[主机ip] [:端口]/数据库名?参数列表
  • 主机ip与端口是可选设置,默认值为127.0.0.1与3306
  • 参数列表采用url编码,格式:参数1=值1&参数2=值2
MySQL连接字符串常用参数
参数名 建议参数值 说明
useSSL true(生产) false(开发) 是否禁用ssl
useUnicode true 启用unicode编码传输数据
characterEncoding UTF-8 使用UTF-8编码传输数据
serverTimezone Asia/Shanghai 使用东8时区时间,UTC+8
allowPublicKeyRetrieval true 允许从客户端获取公钥加密传输
超级异常捕获

选中所需要的代码区域 → Code → Surround With → 6.try…catch

SQL注入攻击 [数据泄露]

当输入部门名称:**’ or 1=1 or 1=’**
只要在or左右两侧有一个成立就都成立的 输入的数据中并没有对单引号加以处理;

package com.imooc.jdbc.hrapp.command;

import java.sql.*;
import java.util.Scanner;

/**
 * 数据查询方法
 */
public class QueryCommand implements Command {
    @Override
    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建Statement对象
            stmt = conn.createStatement();
            //结果集
            System.out.println("select * from employee where dname='" + pdname + "'");
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(stmt != null){
                    stmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

PreparedStatement[解决SQL攻击注入问题(特殊字符转义)]

SQL注入攻击
  • SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为
  • SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理
  • 解决方法:放弃Statement改用PreparedStatement处理SQL
PreparedStatement [变化的地方用问号替代]
  • PreparedStatement预编译Statement是Statement的子接口
  • PreparedStatement对SQL进行参数化, 预防SQL注入攻击
  • PreparedStatement比Statement执行效率更高
  • 要用参数化的sql语句(问号只能出现在值的地方 且不能二次计算)
    String sql = “select * from employee where dname=? and eno > ?“;
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1,pdname); //插入多句 自动寻导入上方sql语句
    pstmt.setInt(2,3500);
//利用PreparedStatement预防SQL注入风险
//当dname值为' or 1=1 or 1=' 时,查询不到任何结果
//SQL:select * from employee where dname = '\' or 1=1 or 1=\”
String sql = "select * from employee where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,dname); //设置SQL参数,参数从1开始
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
    ...
}

JDBC实现写数据

封装DbUtils工具类 [重复代码封装工具类(封装打开和关闭连接方法)]
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

JDBC执行INSERT语句

String sql = "insert into employee(eno,ename) values(?,?)";
PreparedStatement pstmt = conn.PreparedStatement(sql);
pstmt.setInt(1,10);
pstmt.setString(2,"张三");
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate(); //cnt=1
System.out.println("数据新增成功");

JDBC执行UPDATE语句

String sql = "update employee set salary = salary + 1000 where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"研发部");
//executeUpdate方法返回记录数
int cnt = pstmt.exexuteUpdate();
System.out.println(“研发部”+cnt+"名员工提薪1000元");

JDBC执行DELETE语句

String sql = "delete from employee where eno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1,3395);
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate();
System.out.println(cnt+"名员工数据已被删除");

JDBC事务管理方式

  • 事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元
  • 说人话:要么把事情做完,要么什么都不做,不要做一半
  • 事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带
事务的提交操作

应用程序写操作給事务区等全部完成后事务区再commit提交給数据表一次性写入給mysql。提交成功后事务区中的数据被清空

事务的回滚操作

应用程序写操作給事务区,如果+100突然-100程序报错了,由jdbc会向事务区发起rollback回滚操作 清空事务区,最终数据表不会产生任何写操作[要么什么都不做,不要做一半]

JDBC两种事务模式

  • 自动提交事务模式

​ 自动提交模式是指每一次写操作SQL,自动提交事务

自动提交开启方法:
conn.setAutoCommit(true)

​ 自动事务是JDBC默认行为,此模式无法保证多数据一致性[A钱少了 B钱增加]

  • 手动提交事务模式

​ 手动提交模式是指显式调用commit()与rollback()方法管理事务

手动提交开启方法:
conn.setAutoCommit(false)

​ 手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法

实现批量增加员工

package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 1000; i < 2000; i++) {
                if (i==1005){
//                    throw new RuntimeException("插入失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

基于实体类实现分页数据封装

日常开发中如果要对数据进行提取以后最常见的形式是将数据转换为对应的实体类再放到集合中进行保存,即使被关闭数据也不会丢失。

package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

JDBC中Date日期对象的处理

package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}

JDBC批量处理

需要反复执行同时一次性要插入很多数据的操作使用批处理操作

BatchSample.java
package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;

public class BatchSample {
    private static void tc1(){
        //标准未使用批处理
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 100000; i < 200000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc1()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }

    private static void tc2(){
        //使用批处理插入若干数据
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 200000; i < 300000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
//                pstmt.executeUpdate();
                pstmt.addBatch();//将参数加入批处理任务
            }
            pstmt.executeBatch();//执行批处理任务
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc2()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
    public static void main(String[] args) {
        tc1(); //222279ms
        tc2(); //15827 ms
    }
}

综合数据库的增删改查

HumanResourceApplication.java
package test;

import common.*;

import java.util.Scanner;

public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        System.out.println("2-办理员工入职");
        System.out.println("3-调整薪资");
        System.out.println("4-员工离职");
        System.out.println("5-分页查询员工数据");
        System.out.println("请选择功能:");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
            case 2:
                command = new InsertCommand();
                command.execute();
                break;
            case 3:
                command = new UpdateCommand();
                command.execute();
                break;
            case 4:
                command = new DeleteCommand();
                command.execute();
                break;
            case 5:
                command = new PaginationCommand();
                command.execute();
                break;
        }
    }
}
插入员工
InsertCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}
更新员工数据
UpdateCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class UpdateCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        System.out.println("请输入员工新的薪资");
        float salary = in.nextFloat();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "update employee set salary=? where dname=?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,salary);
             pstmt.setInt(2,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工薪资调整完毕");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
删除员工数据
DeleteCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class DeleteCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "delete from employee where eno = ?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工离职手续已完成");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
查找员工数据
PstmtQueryCommand.java
package common;

import java.sql.*;
import java.util.Scanner;

/**
 * PreparedStatement对象使用方法
 */
public class PstmtQueryCommand implements Command {

    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
//        Statement stmt = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建PreparedStatement对象
            String sql = "select * from employee where dname=? and eno > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1,pdname); //注意:参数索引从1
            pstmt.setInt(2,3500);
            //结果集
            rs = pstmt.executeQuery();
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(pstmt != null){
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
分页查询员工数据
PaginationCommand.java
package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}
DbUtils【通用】
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
Employee(entity)
package common.entity;

import java.util.Date;

/**
 * 员工实体类
 */
public class Employee {
    /**
     * 1. 具备默认构造函数
     * 2. 属性私有
     * 3. 存在getter与setter
     */
    public Employee(){

    }
    //通常和数据库数据一一对应
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
}

阿里巴巴Druid连接池[类似于施工仓库 在启动应用时创建连接池]

JDBC先去创建与数据库的连接 比较浪费资源和时间

  • Druid是阿里巴巴开源连接池组件,是最好的连接池之一
  • Druid对数据库连接进行有效管理与重用,最大化程序执行效率
  • 连接池负责创建管理连接,程序只负责取用和归还

/druid-config.properties要放到resources文件夹内

druid-config.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=root
initialSize=10 #初始数量
maxActive=20 #数据库最大连接数量
#最好初始数量=最大数量 一开始j
DruidSample.java
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class DruidSample {
    public static void main(String[] args) {
        //1.加载属性文件
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        //空格->%20 会被转换
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8"); //%20还原回去
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //2.获取DataSource数据源对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //3.创建数据库连接
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,100");
            rs = pstmt.executeQuery();
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

扩展知识:C3P0连接池

不用属性文件,改用c3p0-config.xml保存文件

在里面&无法转义 要写成 ‘ & amp;

idea C3P0时出现java.sql.SQLException: No suitable driver的几种解决办法

1、对lib包Add as library
2、c3p0-config.xml必须放在source目录下,在此目录会被自动读取
3、c3p0命名必须是c3p0-config.xml(至少xml格式是这样)
4、c3p0-config.xml文件配置错误、书写错误

c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <!-- 连接池初始连接数量 -->
        <property name="initialPoolSize">10</property>
        <!--最大连接数量-->
        <property name="maxPoolSize">20</property>
    </default-config>
</c3p0-config>
C3P0Sample.java
package test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import common.DbUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3P0Sample {
    public static void main(String[] args) {
        //1.加载配置文件
        //2.创建DataSource
        DataSource dataSource = new ComboPooledDataSource();
        //3.得到数据库连接
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,10");
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn); //将数据库连接回收到连接池中而不是真正关闭
        }
    }
}

Apache Commos DBUtils

  • commons-dbutils是Apache提供的开源JDBC工具类库
  • 它是对JDBC的简单封装,学习成本极低
  • 使用commons-dbutils可以极大简化JDBC编码工作量
DbUtilsSample.java 【对数据的查询与更新】
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;
import common.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Apache DBUtils + Druid 联合使用演示
 */
public class DbUtilsSample {
    private static void query(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //利用Apache DbUtils大幅简化了数据的提取过程
            QueryRunner qr = new QueryRunner(dataSource); //查询执行者  ↓连接自动关闭 不用手动写代码
            List<Employee> list = qr.query("select * from employee limit ?,10",
                    new BeanListHandler<>(Employee.class),
                    new Object[]{10});//没有结果集只能new 结果自动转换成List实体类 后面的是问号赋值
            for (Employee emp : list){
                System.out.println(emp.getEname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void update(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        Connection conn = null;
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            properties.load(new FileInputStream(propertyFile)); //文件加载
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);//利用工厂类对properties信息进行载入创建对应的datasource对象
            conn = dataSource.getConnection();//获取数据库连接
            conn.setAutoCommit(false);
            String sql1 = "update employee set salary=salary+1000 where eno=?";
            String sql2 = "update employee set salary=salary-599 where eno=?";
            QueryRunner qr = new QueryRunner();
            qr.update(conn, sql1, new Object[]{1000});//写入表操作都用update
            qr.update(conn, sql2, new Object[]{1001});//分别完成加工资和减工资的操作
            conn.commit();//如果都执行成功 就提交 否则回滚
        } catch (Exception e) {
           e.printStackTrace();
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.close(); //对数据库进行回收
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
//        query();
        update();
    }

}
阅读全文

思诚科技项目实训

2023/10/17

html基础知识

打开文件
拖拽法
菜单打开

拓展插件:Chinese、Open in browser

VSCode

代码格式化

  • Tab 往右缩进

  • Shift + Tab 向左回退

  • 一个Tab等于两个空格

  • 设置一个Tab等于2个空格

  • 设置键盘快捷方式 大写与小写 ctrl+shift+u/l

  • shift + alt + ↓ 快速复制上一行

  • ctrl + f 搜索

  • ctrl + h 替换

  • ctrl + z 撤销

  • 多光标修改 选中修改的 ctrl+d 加上上下移动

快速写代码 [按 ctrl+i 自动导入快捷代码]

  • div + Tab 快速输入div块

  • div.red + Tab < div class=”red”> < /div>

  • div#box.red + Tab < div id=”box” class=”red”> < /div>

  • div[name=box] [title=气泡] + Tab < div name=”box” title=”气泡”> < /div>

  • a#link.red[href=#] [ title=我是连接] + Tab < a href=”#” id=”link” class=”red” title=”我是连接”>< /a>

  • div{文本} + Tab < div>aaaa< /div>

  • 输入ul>li{项目} + Tab

    <ul>
      <li>牛逼</li>
    </ul>
    
  • ul#box>li.red[title=’”标题”]{项目1}

    <ul id="box">
      <li class="red" title="标题">项目1</li>
    </ul>
    
  • ui>li*5 一个ul标签和五个li标签 大于号是子类[父子关系]

  • p*3{段落$}

    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
    
  • ul#nav>li.item*5{项目列表$} + Tab

    <ul id="nav">
        <li class="item" 列表1=""></li>
        <li class="item" 列表2=""></li>
        <li class="item" 列表3=""></li>
        <li class="item" 列表4=""></li>
        <li class="item" 列表5=""></li>
    </ul>
    
  • div[name=”box”]>p.red>span*3{文本$} + Tab

    <div name="box">
        <p class="red">
            <span>文本1</span>
            <span>文本2</span>
            <span>文本3</span>
        </p>
    </div>
    <!--#是id    .是class    [name="box"]-->
    
  • h${标题$}*6

    <h1>标题1</h1>
    <h2>标题2</h2>
    <h3>标题3</h3>
    <h4>标题4</h4>
    <h5>标题5</h5>
    <h6>标题6</h6>
    
  • h${标题$}*6 按ctrl+i 自动导入快捷代码

  • (h2{标题}+p{段落})*3 [同类关系]

    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    
  • div#faq>h2{常见问题}dl.list>(dt{问题$}+dd{答案$})*4
    div#faq>(h2{常见问题}+dl.list>(dt{问题$}+dd{答案$})*4)
    
    <div id="faq">
        <h2>常见问题</h2>
        <dl class="list">
            <dt>问题1</dt>
            <dd>答案1</dd>
            <dt>问题2</dt>
            <dd>答案2</dd>
            <dt>问题3</dt>
            <dd>答案3</dd>
            <dt>问题4</dt>
            <dd>答案4</dd>
        </dl>
    </div>
    
  • 输入 ul>li{列表$$}*10 + Tab 确保位数

    <ul>
        <li>列表01</li>
        <li>列表02</li>
        <li>列表03</li>
        <li>列表04</li>
        <li>列表05</li>
        <li>列表06</li>
        <li>列表07</li>
        <li>列表08</li>
        <li>列表09</li>
        <li>列表10</li>
    </ul>
    

MarkDown语法

在VsCode预览ctrl + shift + v

中划线: ’ ~~ 1111 ~~
分割线:’— + 回车
超链接:’[网易] (http://www.163.com)
图片: ‘! [图片名称] (图片URL)

阅读全文

数据库

2023/10/8

数据库基础内容

数据库系统(DBMS)

  • 关系型数据库系统(RDBMS)是指使用了关系模型的数据库
  • 关系模型中,数据是分类存放的,数据之间可以有联系
  • 淘宝网背后是3000多个数据库并发的集群
  • DB2电信金融领域 Oracle数据库集群 MySQL开源灵活 SQL Server教育领域免费
  • NoSQL数据库[Redis]指的是数据分类存放,但是数据之间没有关联关系的数据库系统
    主流NoSQL数据库 => Redis(内存 双十一秒杀) MemCache MongoDB(新闻) Neo4J
    NoSQL数据库只是关系型数据库的补充

MySQL衍生版

Oracle Percona(Linux系统) MariaDB

重设root密码 (D:/temp.txt)

  • 创建一个Txt文件,定义修改密码的SQL语句

    ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; 
    
  • Windows PowerSheell(管理员) 窗口打开
    停止服务

    net stop mysql180
    
    mysqld --defaults-file="D:\MySQL\MySQL Server 8.0\my.ini" --init-file=="D:/temp.txt" --console
    ctrl+C取消
    

    启动服务

    net start mysql180
    

MySQL配置文件

  • my.ini文件中,我们可以设置各种MySQL的配置,例如字符集、端口号、目录地址等等

    my.init{客户端配置信息:[client]… [mysql]… 数据库配置信息:[mysqld]…}

[client]

# pipe=

# socket=MYSQL 端口号

port=3306

# 错误时主板没有轰鸣声
[mysql]
no-beep

# server_type=3
[mysqld]
#端口号
port=3306


# basedir="D:/MySQL/MySQL Server 8.0/"
# Path to the database root
datadir=D:/MySQL/MySQL Server 8.0\Data

# with an account. 密码认证插件
authentication_policy=mysql_native_password

#默认存储引擎
default-storage-engine=INNODB

# database servers. 开启严格模式
sql-mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"

# General and Slow logging. 用文件记录日志
log-output=FILE
# 关闭日志输出
general-log=0
# 日志文件名称
general_log_file="PLUMINARY.log"
# 开启慢查询日志
slow-query-log=1
#慢查询日志文件名称
slow_query_log_file="PLUMINARY-slow.log"
#大于多少秒的执行SQL被记录在慢查询日志
long_query_time=10

# Error Logging.错误日志名称
log-error="PLUMINARY.err"

# from every other ID in use by any other source or replica. 集群会用到数据库ID
server-id=1

# 把表名转换成小写
lower_case_table_names=1

# 导入导出数据的目录地址
secure-file-priv="D:/MySQL/MySQL Server 8.0/Uploads"

#最大连接数
max_connections=151

定义逻辑库、数据表

  • SQL是用于访问和处理数据的标准的计算机语言
SQL语句的注释
# 这是一段注释文字
/* 这是另一段注释文字 */

创建逻辑库

CREATE DATABASE 逻辑库名称; #创建
SHOW DATABASES; #展现逻辑库
DROP DATABASE 逻辑库名称; #删除

创建数据表

CREATE TABLE 数据表(
  列名1 数据类型[约束] [COMMENT 注释],
  列名2 数据类型[约束] [COMMENT 注释],
  ......
)[COMMENT = 注释];


CREATA TABLE student(
  id INT UNSIGNED PRIMARY KEY, #主键约束 不可重复
  name VARCHAR(20) NOT NULL, #varchar 字符串 最大不超过20个字符 NOT NULL必填,不允许没有数据
  sex CHAR(1) NOT NULL, #char 字符
  birthday DATE NOT NULL, 
  tel CHAR(11) NOT NULL,
  remark VARCHAR(200)  #备注不超过200字符串
);

INSERT INTO student VALUES(1,"李强","男","1995-05-15","13312345678",NULL);

SHOW tables; #展现数据表名称
DESC student; #数据表结构具体情况
SHOW CREATE TABLE student; #查询当时的sql语句
DROP TABLE student; #删除数据表

数据定义语言:数据类型

数字

类型 大小 说明
TINYINT 1字节 小整数
SMALLINT 2字节 普通整数
MEDIUMIINT 3字节 普通整数
INT 4字节 较大整数
BIGINT 8字节 大整数
FLOAT 4字节 单精度浮点数
DOUBLE 8字节 双精度浮点数
DECIMAL[精确钱] ——– DECIMAL(10,2)
  • 十进制的浮点数无法在计算机中用二进制精确表达 比如0.2
   num FLOAT(20,10) #位数20 小数点后精确10位  在num中输入0.2的时候 变成了0.200000000030
=> num DECIMAL(20,10)

数据类型:字符串

类型 大小 说明
CHAR 1-255字符 固定长度字符串
VARCHAR 1-65535字符 不固定长度字符串
TEXT 1-65535字符 不确定长度字符串[后不加括号]
MEDIUMETEXT 1-1千6百万字符 不确定长度字符串[后不加括号]
LONGTEXT 1-42亿字符 不确定长度字符串[后不加括号]

数据类型:日期类型(年月日中间横线分割 要加引号)

类型 大小 说明
DATE 3字节 日期
TIME 3字节 时间
YEAR 1字节 年份
DATETIME 8字节 日期时间[电影开始时间]
TIMESTAMP 4字节 时间戳

修改表结构

添加字段
ALTER TABLE 表名称
ADD 列1 数据类型 [约束] [COMMENT 注释],
ADD 列2 数据类型 [约束] [COMMENT 注释],
......;

ADD address VARCHAR(200) NOT NULL;
修改字段名称
ALTER TABLE 表名称
CHANGE 列1 新列名1 数据类型 [约束] [COMMENT 注释],
CHANGE 列2 新列名2 数据类型 [约束] [COMMENT 注释],
......;

修改字段
ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL;
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名
删除字段
ALTER TABLE 表名称
DROP 列1,
DROP 列2,
......;

数据库的范式

第一范式:原子性

  • 第一范式是数据库的基本要求,不满足组这一点就不是关系数据库
  • 数据库的每一列都是不可分割的基本数据项,同一列中不能有多个值,也不能存在重复的属性
不符合第一范式
学号 姓名 班级
1000 刘娜 高三年级1班
符合第一范式
学号 姓名 年纪 班级
1000 刘娜 高三 1班

第二范式:唯一性

  • 数据表中的每条记录必须是唯一的。为了实现区分,通常要为表加上一列用来存储唯一标识,这个唯一属性列被称为主键列
无法区分重复的数据
学号 考试成绩 日期
230 58 2018-07-15
230 58 2018-07-15
数据具有唯一性
流水号 学号 考试成绩 日期
201807152687 230 58 2018-07-15
201807152694 230 58 2018-07-15

第三范式:关联性

  • 每列都与主键有直接关系,不存在传递依赖
违反第三范式
爸爸 儿子 女儿 女儿的玩具 女儿的衣服
陈华 陈浩 陈婷婷 海绵宝宝 校服

拿爸爸作为主键 儿子和女儿字段都是依赖于爸爸字段 但是后面的字段违反了第三范式 女儿的玩具和女儿的衣服是依赖于女儿这个字段的并不依赖于爸爸这个字段
数据表关系都非常松散 在检索的时候非常慢 因为爸爸主键字段查询非常快 但是查女儿的玩具和女儿的衣服找不到一样的主键

遵守第三范式
爸爸 儿子 女儿
陈华 陈浩 陈婷婷
女儿 女儿的玩具 女儿的衣服
陈婷婷 海绵宝宝 校服
  • 依照第三范式,数据可以拆分保存到不同的数据表,彼此保持关联
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22

部门字段也是依赖于编号的 两张表没有违反第三范式

字段约束

  • MySQL中的字段约束共有四种:
约束名称 关键字 描述
主键约束 PRIMARY KEY 字段值唯一,且不能为NULL
非空约束 NOT NULL 字段值不能为NULL
唯一约束 UNIQUE 字段值唯一,且可以为NULL
外键约束 FOREIGN KEY 保持关联数据的逻辑性

主键约束

  • 主键约束要求字段的值在全表必须唯一,而且不能为NULL值
  • 建议主键一定要使用数字类型,因为数字的检索速度会非常快
  • 如果主键是数字类型,还可也设置自动增长
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
    ......
);

非空约束

  • 非空约束要求字段的值不能为NULL值
  • NULL值以为没有值,而不是 “” 空字符串
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    married BOOLEAN NOT NULL DEFAULT FALSE #若不写则是默认值false
);

唯一约束

  • 唯一约束要求字段值如果不为NULL,那么在全表必须唯一
CREATE TABLE t_tracher(
    ......
    tel CHAR(11) NOT NULL UNIQUE
);
t_  tb_  一般是真实的表  
v_ vw_   一般是视图虚拟表的意思
总结代码
CREATE TABLE t_teacher(
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    tel CHAR(11) NOT NULL UNIQUE, #唯一约束 UNIQUE 字段值唯一,且可以为NULL
    married BOOLEAN NOT NULL DEFAULT FALSE
);
-------------------------------------------------------------------------
CREATE TABLE student(
    id INT UNSIGNED PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    sex CHAR(1) NOT NULL,
    birthday DATE NOT NULL,
    tel CHAR(11) NOT NULL,
    remark VARCHAR(200)
);
INSERT INTO student VALUES (1,"李强","男","1995-05-15","13312345678",NULL);
DESC student; 
SHOW CREATE TABLE student;

ALTER TABLE student
ADD address VARCHAR(200) NOT NULL, #添加字段信息
ADD home_tel CHAR(11) NOT NULL;

ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL; #改字段类型信息

ALTER TABLE student
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名

ALTER TABLE student
DROP address; #删除字段

外键约束

  • 外键约束用来保证关联数据的逻辑关系
  • 外键约束的定义是写在子表上的
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22
CREATE TABLE t_dept(
    deptno INT UNSIGNED PRIMARY KEY,
    dname VARCHAR(20) NOT NULL UNIQUE,
    tel CHAR(4) UNIQUE #写了电话必须唯一
);
CREATE TABLE t_emp(
    empno INT UNSIGNED PRIMARY KEY,
    ename VARCHAR(20) NOT NULL,
    sex EUNM("男","女") NOT NULL, #枚举 选择其中一个
    deptno INT UNSIGNED,
    hiredate DATE NOT NULL,
    FOREIGN KEY (deptno) REFERENCES t_dept(deptno) #和父表进行外键约束关联
);

不允许删除第一个编号 需要先删除第二个表的记录 再删除第一个表记录 逻辑关系有保证

外键约束的闭环问题 【因此开发中放弃外键约束】

  • 如果形成外键闭环,我们将无法删除任何一张表的记录

数据排序的好处

  • 一旦数据排序后,查找的速度就会翻倍,现实世界跟程序世界都是如此

如何创建索引

CREATE TABLE 表名称(
    ......,
    INDEX [索引名称] (字段),
    ......
);

#数据库对这个索引字段排序生成二叉树 每个字段都是有索引名称的
CREATE TABLE t_message(
    id INT UNSIGNED PRIMARY KEY,
    content VARCHAR(200) NOT NULL,
    type ENUM("公告","通报","个人通知") NOT NULL,
    create_time TIMESTAMP NOT NULL,
    INDEX idx_type (type)
); #利用二叉树的二分查找去查找索引字段就会非常快

如何添加与删除索引

CREATE INDEX 索引名称 ON 表名(字段); #添加索引
ALTER TABLE 表名称 ADD INDEX [索引名](字段); #添加索引
SHOW INDEX FORM 表名; #展示索引表  PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
DROP INDEX 索引名称 ON 表名; #删除索引

索引的使用原则

  • 数据量很大,而且经常被查询的数据表可以设置索引 (日志表很少查询 无需设置 因为数据库要维护二叉树)
  • 索引只添加在经常被用作检索条件的字段上面
  • 不要在大字段上创建索引

(以上均为DDL语句)

DML语句

数据操作语句:普通查询

记录查询
  • 最基本的查询语句是由SELECTFROM关键字组成
USE demo;
SELECT * FROM t_emp; #FROM是从员工表查数据 *是在结果集里显示员工表所有字段
SELECT empno,ename,sal FROM t_emp; #不查询所有 单独查询想要的
  • SELECT语句屏蔽了物理层的操作,用户不必关心数据的真实存储,交给数据库高效查找数据
  • 通常情况下 ,SELECT子句中使用了表达式,那么这列的名字就默认为表达式,因此需要一种对列明重命名的机制 [起别名:只是对查询的结果集字段改名称]
SELECT
  empno,
  sal*12 AS "income"
FROM t_emp;
  • 上述sql 查询语句的子句执行顺序

词法分析与优化:读取SQL语句
FROM:选择数据来源
SELECT:选择输出内容

数据操作语言:数据分页

  • 朋友圈只会加载少量部分信息,不用一次性加载全部朋友圈,那样只会浪费CPU时间、内存和网络带宽
  • 如果结果集的记录很多,则可以使用LIMIT关键字限定结果集数量
SELECT ... FROM ... LIMIT 起始位置,偏移量; 
SELECT empno,ename FROM t_emp LIMIT 0,20; #从0往后取20条数据
数据分页简写
  • 如果LIMIT子句只有一个参数,它表示的是偏移量,起始值默认为0
SELECT empno,ename FROM t_emp LIMIT 10;
SELECT empno,ename FROM t_emp LIMIT 0,10;
FROM → SELECT → LIMIT

结果集排序

  • 如果没有设置,查询语句不会对结果集进行排序。也就是说,如果想让结果集按照某种顺序排序,就必须使用ORDER BY子句 【默认升序
SELECT ... FROM ... ORDER BY 列名 [ASC|DESC];
SELECT ename,sal FROM t_emp ORDER BY sal;

排序关键字

  • ASC代表升序(默认),DESC代表降序
  • 如果排序列是数字类型,数据库就按照数字大小排序,如果是日期类型就按照日期大小排序,如果是字符串就暗战字符集序号排序。
SELECT ename,sal FROM t_emp ORDER BY hiredate DESC;
排序字段内容相同的情况
  • 如果两条数据排序字段内容相同 sal 都是3000
    默认情况下是按照主键升序
多个排序字段
  • 使用ORDER BY规定首要排序顺序条件和次要排序条件。数据库会先按照要排序条件排序,如果遇到首要排序内容相同的记录,那么就会启动次要排序条件接着排序
SELECT ename,sal,hiredate 
FROM t_emp 
ORDER BY hiredate DESC,sal ASC;
#先按照首要排序hiredate降序 再启动次要排序sal降序

SELECT ename,sal,hiredate
FROM t_emp
ORDER BY sal DESC 
LIMIT 0,5  #工资排在前五位进行降序
排序+分页
  • ODER BY 子句书写的时候放在LIMIT子句的前面
    FROM → SELECT → ORDER BY → LIMIT

结果集中的重复数据

  • 假如我们要查询员工表有多种职业,写出来的sql语句

    SELECT job FROM t_emp; #结果集内可能会出现重复记录
    
  • 如果去除重复的数据,可以使用 DISTINCT 关键字来实现

    SELECT DISTINCT 字段 FROM ...;
    SELECT DISTINCT job FROM t_emp;
    
注意事项
  • 使用DISTINCTSELECT子句中只能查询一列数据,如果查询多列,去除重复记录就会失效

    SELECT DISTINCT job,ename FROM t_emp; #有job相同但是ename不相同 不能查询
    
  • DISTINCT关键字只能再SELECT子句中使用一次 [必须放在第一个字段前面]

数据操作语言:条件查询(一)

  • 满足某一种或几种条件的记录。这类条件要用WHERE子句来实现数据的筛选

    SELECT ... FROM ... WHERE 条件 [AND|OR] 条件 ...;
    
    SELECT empno,ename,sal FROM t_emp
    WHERE deptno=10 AND sal>=2000;
    
    SELECT empno,ename,sal
    FROM t_emp
    WHERE(deptno=10 OR deptno=20) AND sal>=2000;
    
四类运算符
  • WHERE语句中的条件运算会用到以下四种运算符

    序号 运算符
    1 数学运算符
    2 比较运算符
    3 逻辑运算符
    4 按位运算符
算数运算符 [+加 -减 *乘 /除 %模]

NULL值与任何数字加减乘除都是NULL值 如果想要运算 必须加入 IFNULL(null,0); 意思是遇到NULL值就用0来计算 10+IFNULL(null,0) = 10
DATEDIFF(入职日期-现在的日期)/365

从t_emp表中找出 号位是10 和 总工资≥15000 的并且 计算工龄超过20年的人
SELECT empno,ename,sal,hiredate
FROM t_emp
WHERE deptno=10 AND (sal+IFNULL(NULL,0))*12>=15000
AND DATEDIFF(NOW(),hiredate)/365>=20;
比较运算符 [>大于 >=大于等于 <小于 <=小于等于 =等于 !=不等于 IN包含deptno IN(10,30,40)]
查询10 20 30部门里面在1980年以前入职的员工而且不能是SALESMAN职位
SELECT
  empno,ename,sal,deptno,hiredate
FROM t_emp;
WHERE deptno IN(10,20,30) AND job!="SALESMAN"
AND hiredate<"1985-01-01";
+续比较运算符
序号 表达式 意义 例子
8 IS NULL 为空 comm IS NULL
9 IS NOT NULL 不为空 comm IS NOT NULL
10 BETWEEN AND 范围 sal BETWEEN 2000 AND 3000
11 LIKE 模糊查询 ename LIKE “A%”
12 REGEXP 正则表达式 ename REGEXP “[a-zA-Z]{4}”

__代表前方一个未知 %代表前方N个未知

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename LIKE "_LAKE";

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename REGEXP "^[\\u4e00-\\u9fa5]{2,4}$"; #正则表达汉字范围寻找两到四个中文字符
按位运算符 [&位与 |位或 ~位取反 ^位异或 <<左移 >>右移]

<< 左移 10<<1 把10转换成二进制位 在最右面补上一个0 整体向左移动了一个单位
》》左移 10<<1 把10转换成二进制位 在最右面抹去一个0 整体向右移动了一个单位

二进制按位运算

  • 二进制运算的实质是将参与运算的两个操作数,按对应的二进制数逐位进行逻辑运算
    SELECT 3 & 7; 0011 & 0111 = 0011 = 3

数据操作语言:条件查询(二) [AND与 OR或 NOT非 XOR异或]

查询10和20之外部门的信息
SELECT
  ename,deptno,sal
FROM t_emp
WHERE NOT deptno IN(10,20) XOR sal>=2000;

WHERE子句的注意事项

  • WHERE子句中,条件执行的顺序是从左到右的。所以我们应该把索引条件,或者筛选掉记录最多的条件写在最左侧
    SELECT empno,ename FROM t_emp
    WHERE ename = "FORD" AND sal >= 2000;
    
    SELECT empno,ename FROM t_emp
    WHERE deptno = 10 AND sal >= 2000;
    

各种子句的执行排序

FROM → WHERE → SELECT → ORDER BY → LIMIT

先表 再查出符合条件的记录 才能从中挑选出符合的字段 先排序后限制


数据库高级内容

数据操作语言:聚合函数

  • 聚合函数在数据的查询分析中,应用十分广泛。聚合函数可以对数据求和、求最大值最小值、求平均值等等

  • 求公司员工的平均月收入是多少?

    底薪+佣金(不是null)
    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
SUM函数
  • SUM函数用于求和,只能用于数字类型,字符类型的统计结果为0,日期类型统计结果是毫秒数相加

    SELECT SUM(ename) FROM t_emp
    
    SELECT SUM(sal) FROM t_emp
    WHERE deptno IN (10,20);
    
MAX函数
  • MAX函数用于获得非空值的最大值

    SELECT MAX(comm) FORM t_emp;
    
    ①查询10和20部门中,月收入最高的员工?
    SELECT 
    MAX(sal+IFNULL(comm,0)) FROM t_temp
    FROM t_emp
    WHERE deptno IN (10,20);
    
    ②查询员工名字最长的是几个字符?
    SELECT MAX(LENGTH(ename)) FROM t_emp;
    
    SELECT
    SUM(sal),MAX(sal+IFNULL(comm,0)) 
    FROM t_emp
    WHERE deptno IN(10,20); 
    
MIN函数
  • MIN函数用于获得非空值的最小值

    SELECT MIN(empno) FROM t_emp;
    
AVG函数
  • AVG函数用于获得非空值的平均值,非数字数据统计结果为0

    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
COUNT函数
  • COUNT(*) [找所有]用于获得包含空值的记录数,COUNT(列名)用于获得包含非空值的记录数

    SELECT COUNT(*) FROM t_emp; #统计所有   15 
    SELECT COUNT(comm) FROM t_emp; #统计数量是非空   5
    
  • 查询10和20部门中,底薪超过2000元并且工龄超过15年的员工人数

    SELECT COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20) 
    AND sal>=2000
    AND DATEDIFF(NOW(),hiredate)/365>=15;
    
  • 查询1985年以后入职的员工,底薪超过公司平均底薪的员工数量?

    ×××××××××错误示范×××××××××
    SELECT COUNT(*) FROM t_emp
    WHERE hiredate>="1985-01-01"
    AND sal>AVG(sal); #AVG无法运行 聚合函数不能出现在WHERE里面
    

数据操作语言:分组查询

为什么要分组?
  • 默认情况下汇总函数是对全表范围内的数据做统计
  • GROUP BY子句的作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对每个小区域分别进行数据汇总处理
SELECT deptno,ROUND(AVG(sal))
FROM t_emp
GROUP BY deptno; #分组来计算AVG

逐级分组

  • 数据库支持多列分组条件,执行的时候逐级分组
  • 查询每个部门里,每种职位的人员数量和平均底薪
#按照部门和工作分组 ↓这种职位人数 ↓这种职位底薪平均值
SELECT deptno,job,COUNT(*),AVG(sal)
FROM t_emp 
GROUP BY deptno,job
ORDER BY deptno; #按照deptno去排序

对SELECT子句的要求

  • 查询语句中如果含有GROUP BY子句,那么SELECT子句中的内容就必须要遵守规定:SELECT子句中可以包括聚合函数,或者GROUP BY子句的分组列,其余内容均不可以出现在SELECT子句中
#正确示范
SELECT deptno,COUNT(*),AVG(sal)
FROM t_emp GROUP BY deptno;

#错误示范
SELECT deptno,COUNT(*),AVG(sal),sal
FROM t_emp GROUP BY deptno;

对分组结果集再次做汇总计算

SELECT 
deptno,COUNT(*),AVG(sal),MAX(sal),MIN(sal)
FROM t_emp 
GROUP BY deptno WITH ROLLUP; #WITH ROLLUP对汇总函数再次进行汇总运算

GROUP_CONCAT函数

  • GROUP_CONCAT函数可以把分组查询中的某个字段拼接成一个字符串

  • 查询每个部门内底薪超过2000元的人数和员工姓名

    SELECT deptno,GROUP_CONCAT(ename),COUNT(*)
    FROM t_emp 
    WHERE sal>=2000
    GROUP BY deptno;
    
    deptno COUNT(*) GROUP_CONCAT(ename)
    10 2 CLARK,KING
    20 3 JONES,SCOTT,FORD
    30 1 BLAKE

各种子句的执行顺序

FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT

WHERE符合的留下来交给GROUP BY去分组之后调用SELECT中的聚合函数计算 ORDER BY子句对结果排序交给LIMIT子句来挑选返回哪些数据

分组查询遇到的困难?

  • 查询部门平均底薪超过2000元的部门编号

    #错误演示[因为WHERE语句出现了聚合函数]
    SELECT deptno FROM t_emp
    WHERE AVG(sal)>=2000
    GROUP BY deptno;
    
  • HAVING语句是紧紧跟着GROUP BY语句的 HAVING子句可以写聚合函数作为判断条件

    SELECT deptno
    FROM t_emp
    GROUP BY deptno HAVING AVG(sal)>=2000;
    

HAVING子句的用途

  • 查询每个部门中,1982年以后入职的员工${普通条件可以写在WHERE里}$超过2个人${COUNT(*)>=2}$的部门编号
    不能拿聚合函数某一个字段做判断

    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2;
    ORDER BY deptno ASC;
    
    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2 AND AVG(sal)>=2000;
    

HAVING子句的特殊用法 [作用类似于WHERE]

  • 按照数字1分组,MySQL会根据SELECT子句中的列进行分组,HAVING子句也可以正常使用
    能用WHERE就不要先用HAVING 它的作用是給聚合函数做判断

    #不推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    GROUP BY 1 HAVING deptno IN (10,20);
    
    #推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20)
    GROUP BY 1;
    

数据操作语言:表链接查询(一)

从多张表中提取数据
  • 从多张表中提取数据,必须指定关联的条件。如果不定义关联条件就会出现无条件链接,两张表的数据会交叉连接,产生笛卡尔积

  • 规定了链接条件的表链接语句,就不会出现笛卡尔积 [On条件]

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e JOIN t_dept d #給表起别名
    ON e.deptno=d.deptno; #员工部门编号等于部门的部门编号
    

表链接的分类

  • 表链接分为两种:内链接外连接
  • 内链接是结果集中只保留符合连接条件的记录
  • 外连接是不管符不符合链接条件,记录都要保留在结果集中
内链接
SELECT ... FROM 表1
[INNER] JOIN 表2 ON 条件
[INNER] JOIN 表3 ON 条件
...
内连接的多种语法形式
SELECT ... FROM 表1 JOIN 表2 ON 连接条件;
SELECT ... FROM 表1 JOIN 表2 WHERE 连接条件
SELECT ... FROM 表1,表2 WHERE 连接条件;

内连接练习1

  • 查询每个员工的工号、姓名、部门名称、底薪、职位、工资等级?且保证工资符合范围
SELECT e.empno,e.ename,d.dname,e.sal,e.job,s.grade
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal;
  • 内连接的数据表不一定必须有同名字段,只要字段之间符合逻辑关系就可以

内连接练习2

  • 查询与SCOTT相同部门的员工都有谁?

  • 相同的数据表也可以做表连接

    SELECT deptno
    FROM t_emp
    WHERE ename="SCOTT"; 
    
    #子查询的结果变成了条件  SCOTT本人不算
    SELECT deptno
    FROM t_emp
    WHERE deptno=(SELECT dptno FROM t_emp WHERE ename="SCOTT");
    AND ename!="SCOTT";
    
    #改造快速一些  ON后是筛选条件 和 WHERE作用差不多
    SELECT e2.ename
    FROM t_emp e1 JOIN t_emp e2 ON e1.deptno=e2.deptno
    WHERE e1.ename="SCOTT" AND e2.ename!="SCOTT"; 
    

数据操作语言:表链接查询(二)

内连接查询练习1
  • 查询底薪超过公司平均底薪的员工信息?
    把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
#错误展示
SELECT e2.empno,e2.ename,e2.sal  #因为ON可以换成WHERE 然而WHERE后面有聚合函数 所以会报错
FORM t_emp e1 JOIN t_emp e2 ON e2.sal>=AVG(e1.sal);

#把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
SELECT AVG(sal)
FROM t_emp;

SELECT e.empno,e.ename,e.sal
FROM t_emp e JOIN(SELECT AVG(sal) avg FROM t_emp) t #avg别名
ON e.sal>=t.avg;
  • 查询RESEARCH部门的人数、最高底薪、最低底薪、平均底薪、平均工龄?
SELECT COUNT(*),MAX(e.sal),MIN(e.sal),AVG(e.sal),AVG(DATEDIFF(NOW(),e.hiredate)/365)
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
WHERE d.dname="RESEARCH";

SELECT FLOOR(28.9); #变成28 向上取整
SELECT CEIL(1.1);   #变成1  向上取整
内连接查询练习2
  • 查询每种职业的最高工资、最低工资、平均工资、最高工资等级和最低工资等级?

    SELECT 
    e.job,MAX(e.sal+IFNULL(e,comm,0)),
    MIN(e.sal+IFNULL(e,comm,0)),AVG(e.sal+IFNULL(e,comm,0)),
    MAX(s.grade),MIN(s.grade)
    FROM t_emp e JOIN t_salgrade s
    ON (e.sal+IFNULL(e,comm,0)) BETWEEN s.losal AND s.hisal
    GROUP BY e.job;
    
  • 查询每个底薪超过部门平均底薪的员工信息 [用表连接 而不是子查询]

    SELECT e.empno,e.ename,e.sal
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg FROM t_emp GROUP BY dptno) t #别名t
    ON e.deptno=t.deptno AND e.sal>=t.avg;
    

数据操作语言:表链接查询(三)

为什么要使用外连接
  • 如果说陈浩是一名临时人员,没有固定的部门编制(NULL),那么我们想查询每名员工和他的部门名称,用内连接就会遗漏掉陈浩,所以要引用外连接的语法才能解决这个问题
外连接简介
  • 外连接与内连接的区别在于,除了符合条件的记录之外,结果集中还会保留不符合条件的记录

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e  #因为陈浩部门编制是NULL 不能直接JOIN 只能LEFT JOIN
    LEFT JOIN t_dept d ON e.deptno=d.deptno; 
    
    7902 FORD RESEARCH
    7934 MILLER ACCOUNTING
    8000 陈浩 (NULL)

左连接和右连接

  • 左外连接就是保留左表所有的记录,与右表做连接。如果右表有符合条件的记录就与左表连接。如果右表没有符合条件的记录,就用NULL与左表连接。右外连接也是如此。

    SELECT e.empno,e.ename,d.dname
    FORM t_dept d RIGHT JOIN t_emp e #要保留所有的t_emp e
    ON e.deptno=d.deptno;
    

外连接练习1

  • 查询每个部门的名称和部门的人数? [40有部门没员工 要保存空值]
    左外连接把部门表写在左侧 右外连接把部门表写在右侧

    SELECT d.dname,COUNT(*)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录 右表有空
    ON d.deptno=e.deptno
    GROUP BY d.deptno; #因为保留了左表所有记录 所以分组按照部门号分
    #最终有一条t_dept与t_emp中的NULL做连接 所以 COUNT算上此记录
    #若要将右表的NULL值忽略掉 要在COUNT(d.deptno)这样写
    
    SELECT d.dname,COUNT(d.deptno)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录
    ON d.deptno=e.deptno
    GROUP BY d.deptno;
    
    dname COUNT(d.deptno)
    ACCOUNTING 3
    RESEARCH 5
    SALES 6
    OPERATIONS 0
  • 查询每个部门的名称和部门的人数?如果没有部门的员工,部门名称用NULL代替

  • UNION关键字可以将多个查询语句的结果集进行合并

    (查询语句) UNION (查询语句) UNION (查询语句)...
    (SELECT d.name,COUNT(e.deptno)
    FROM t_dept d LEFT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    )UNION
    (SELECT d.dname,COUNT(*)
    FROM t_dept d RIGHT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    );
    

数据库操作语言:表连接查询(四)

  • 查询每名员工的编号、姓名、部门、月薪、工资等级、工龄、上司编号、上司姓名、上司部门?
    [不知道:员工信息 员工的上司信息 两个不知道信息是不能使用子查询的 员工表 部门表 公司等级表]

    #陈浩要保存下来 用外连接
    SELECT
    e.empno,e.ename,d.dname,
    e.sal+IFNULL(e.comm,0),s.grade,
    FLOOR(DATEDIFF(NOW(),e.hiredate)/365),
    t.empno AS mgrno,t.ename AS mname,t.dname AS mdname #定义上司的数据
    FROM t_emp e 
    LEFT JOIN t_dept d ON e.deptno=d.deptno #结果集所有记录保存下来跟工资等级表做连接
    LEFT JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal
    LEFT JOIN
    (SELECT e1.empno,e1.ename,d1.dname
    FROM t_emp e1 JOIN t_dept d1
    ON e1.deptno=d1.deptno
    )t ON e.mgr=t.empno;
    

外连接的注意事项

  • 内连接值保留符合条件的记录,所以查询条件写在ON子句和WHERE子句中的效果是相同的。但是外连接里,条件写在WHERE子句里,不符合条件的记录是会被过滤掉的,而不是保留下来的。

数据操作语言:子查询(一)

WHERE中的子查询是需要反复查询的 不推荐使用,但是把它所得的结果集作为一张表跟其他表做连接是推荐的

  • 子查询是一种查询中嵌套查询的语句

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
子查询的分类
  • 子查询可以写在三个地方:WHERE子句、FROM子句、SELECT子句,但是只有FROM子句子查询是最可取的
WHERE子查询
  • 这种子查询最简单,最容易理解,但是确实效率很低的子查询

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp #↓↓↓↓ 比较每条记录都要重写执行子查询 ↓↓↓↓
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
FROM子查询
  • 这种子查询只会执行一次,所以查询效率很高

  • 查询底薪超过公司平均底薪的员工信息

    SELECT
    e.empno,e.ename,e.sal,t.avg
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg
    FORM t_emp 
    GROUP BY deptno) t #按照部门编号去分组 起别名
    ON e.deptno=t.deptno 
    AND e.sal>=t.avg;
    
SELECT子查询
  • 这种子查询每输出一条记录的时候都要执行一次,查询效率很低

    SELECT
    e.empno,
    e.ename,
    (SELECT dname FROM t_dept WHERE deptno=e.deptno)
    FROM t_emp e;
    

数据操作语言:子查询(二)

单行子查询和多行子查询 [结果集可以作为新表连接]
  • 单行子查询的结果集只有一条记录,多行子查询结果集有多行记录

  • 多行子查询只能出现在WHERE子句和FROM子句中

  • 如何用子查询查找FORD和MARTIN两个人的同事

    SELECT ename #排除那俩人之外
    FROM t_emp 
    WHERE
    deptno IN    # deptno = 不行因为后面返回了两条记录
    (SELECT deptno FROM t_emp WHERE ename IN("FORD","MARTIN")); #返回两条记录
    AND ename NOT IN("FORD","MARTIN");
    

WHERE子句中的多行子查询

  • WHERE子句中,可以使用IN、ALL、ANY、EXISTS关键字来处理多行表达式结果集的条件判断

  • 调查”FORD”和”MARTIN”底薪都高的员工信息

    SELECT ename 
    FROM t_emp 
    WHERE sal > ALL #ALL是sal里的数比结果集里的所有值都大  ANY则是比任何一个人大
    (SELECT sal FROM t_emp #结果集里返回了多条记录
    WHERE ename IN("FORD","MARTIN"))
    AND ename NOT IN("FORD","MARTIN"); #不包含这俩人
    

EXISTS关键字

  • EXISTS关键字是把原来在子查询之外的条件判断,写到了子查询的里面
    EXISTS用上之后WHERE就不写任何语句了

    SELECT ... FROM 表名 WHERE [NOT] EXISTS(子查询);
    
  • 查询工资等级是3级或者4级的员工信息

    #排斥此方法 效率低下
    SELECT
    FROM t_emp
    WHERE EXISTS(
    SELECT * FROM t_salgrade
    WHERE sal BETWEEN losal AND hisal 
    AND grade IN(3,4)
    );
    

MySQL对数据的基本操作

数据操作语言:INSERT语句

  • INSERT语句可以向数据表写入记录,可以是一条记录,也可以是多条记录

    INSERT INTO 表名(字段1,字段2,......) #添加字段可以快速写入 
    VALUES(值1,值2,......);
    
    INSERT INTO 表名(字段1,字段2,......) 
    VALUES(值1,值2,......),(值1,值2,......); #多条记录
    
    INSERT INTO t_dept(deptno,dname,loc)
    VALUES(520,"研发部","河北"),(250,"销售部","江西");
    
    #向技术部添加一条员工记录 
    #[技术部编号不知道 子查询技术部的编号 结果 写到VALUES子句里面通过INSERT插入员工表里面]
    INSERT INTO t_emp
    (empno,ename,job,mgr,hiredate,sal,comm,deptno)
    VALUES(8001,"潘春尧","SALESMAN",8000,"2023-10-15",2000,NULL,
    (SELECT deptno FROM t_dept WHERE dname="技术部")); #子查询单个结果返回
    

INSERT语法方言

  • MySQL的INSERT语句有一种方言语法

    INSERT INTO 表名 SET 字段1=值1,字段2=值2,......; #只适合Mysql数据库
    
    (INSERT) INTO t_emp
    SET empno=8002,ename="JACK",job="SLAESMAN",mgr=8000,
    hiredate="1985-01-01",sal=2500,comm=NULL,deptno=50;
    
    DELETE FROM t_emp WHERE empno=8002;
    

IGNORE关键字

  • IGNORE关键字会让INSERT只插入数据库不存在的记录

    INSERT [IGNORE] INTO 表名...;
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"技术部","北京"); #40编号是主键被占用了 和已经现存的记录有冲突
    
    #运用方言写
    INSERT IGNORE INTO t_dept
    SET deptno=40,dname="技术部",loc="北京";
    
    #多重数据忽略不正确的数据
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"A","北京"),(80,"B","上海");
    
    #VALUES(40,"A","北京"),(80,"B","上海")
    #> Affected rows: 1
    #> 时间: 0.002s
    

数据操作语言:UPDATE语句(一)

  • UPDATE语句用于修改表的记录
    UPDATE中的NIGNORE直接忽略冲突的语句

    UPDATE [IGNORE] 表名
    SET 字段1=值1,字段2=值2,......
    [WHERE 条件1...] #有条件的修改记录
    [ORDER BY...] #对数据先排序 后修改员工编号+1 
    [LIMIT...]; #取分页数据
    
    #把每个员工的编号和上司的编号+1,用ORDER BY子句完成
    UPDATE t_emp 
    SET empno=empno+1,mgr=mgr+1
    ORDER BY empno DESC;
    
    #把用收入前三名的员工底薪减100元,用LIMIT子句完成
    UPDATE t_emp
    SET sal=sal-100 
    ORDER BY sal+IFNULL(comm,0) DESC #降序 
    LIMIT 3; #取前三条记录
    

UPDATE语句的表连接(一)

  • 因为相关子查询效率非常低,所以我们可以利用表连接的方式来改造UPDATE语句

    UPDATE 表1 JOIN 表2 ON 条件
    SET 字段1=值1,字段2=值2,......;
    
  • 表连接的UPDATE语句可以修改多张表的记录[进化]

    UPDATE 表1,表2
    SET 字段1=值1,字段2=值2,......
    WHERE 连接条件;
    
    #把ALLEN调往RESEARCH部门,职务调整为ANALYST
    [员工表+部门表]
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    #//在t_emp表中找到ALLEN + 在t_dept表中找到RESEARCH 
    #//然后把d.deptno赋值給e.deptno
    
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno,e.job="ANALYST",d.loc="北京"
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    
  • 把底薪低于公司平均底薪的员工,底薪增加150元
    [运用表连接的方法比WHERE语句的效率高]

    UPDATE t_emp e JOIN
    (SELECT AVG(sal) AS avg FROM t_emp) t
    ON e.sal<t.avg
    SET e.sal=e.sal+150;
    

UPDATE语句的表连接(二)

  • UPDATE语句的表连接既可以是内连接,又可以是外连接

    UPDATE 表1[LEFT|RIGHT] JOIN 表2 ON 条件 
    SET 字段1=值1,字段2=值2,...;
    
  • 把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    [把所有员工保留下来 但是陈浩没有部门 用内连接的话会把陈浩忽略掉 要用左外连接把左表所有数据保留下来再去跟部门表作连接]

    #把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno #连接条件 不符合的也保留
    SET e.deptno=202
    WHERE e.deptno IS NULL OR (d.dname="SALES" AND e.sal<2000);
    

数据操作语言: DELETE语句(一)

  • DELETE语句用于删除记录
    DELETE [IGNORE] FROM 表名
    [WHERE 条件1,条件2,...] #按照条件删除记录
    [ORDER BY...] #排序后删除
    [LIMIT...]; #分页 工资降序排序 排在前五名的删掉
    #顺序依次向下 最后一个是DELETE
    
练习1
  • 删除10部门中,工龄超过20年的员工记录

    DELETE FROM t_emp
    WHERE deptno=10 AND DATEDIFF(NOW(),hiredate)/365>=20;
    
  • 删除20部门中工资最高的员工记录

    DELETE FROM t_emp
    WHERE deptno=20
    ORDER BY sal+IFNULL(comm,0) DESC
    LIMIT 1;
    

DELETE语句的表连接(一)

  • 因为相关子查询效率非常低,所有我们可以利用表连接的方法来改造DELETE语句

    DELETE 表1,...FROM 表1 JOIN 表2 ON 条件 #删除哪张表记录的操作
    [WHERE 条件1,条件2,...]
    [ORDER BY...]
    [LIMIT...];
    
    #删除SALES部门和该部门的全部员工记录
    #无需外连接 因为没有部门需要不删除的
    DELETE e,d
    FROM t_emp e JOIN t_dept d NO e.deptno=d.deptno #部门名称
    WHERE d.dname="SALES";
    
  • 删除SALES部门和该部门的全部员工记录[表连接]

    DELETE e
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS sal FROM t_emp GOURP BY deptno)
    ON e.deptno=t.deptno AND e.sal<t.sal
    
  • 删除员工KING和他的直接下属的员工记录,用表连接实现

    DELETE e
    FROM t_emp e JOIN
    (SELECT empno FROM t_emp WHERE ename="KING") t
    ON e.mgr=t.empno OR e.empno=t.empno; #KING的下属 OR KING这个人
    

DELETE语句的表连接(二)

  • DELETE语句的表连接既可以是内连接,又可以是外连接

    DELETE 表1,... FROM 表1 [LEFT|RIGHT] JOIN 表2 ON 条件...;
    
  • 删除SALES部门的员工,以及没有部门的员工 [左外 因为陈浩没部门要保留 外连接(不可用内连接)]

    DELETE e
    FROM t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    WHERE d.dname="SALES" OR e.deptno IS NULL
    

快速删除数据表全部记录

  • DELETE语句实在事务机制下删除记录,删除记录之前,先把将要删除的记录保存到日志文件里面,然后删除记录

  • TRUNCATE语句再事务机制之外删除记录,速度大于DELETE语句

    TRUNCATE TABLE 表名;
    

MySql的内置函数

数据库函数分类:数字、字符、日期、条件函数

函数 功能 用力
ABS 绝对值 ABS(-100)
ROUND 四舍五入 ROUND(4.62)
FOLLR 强制舍位到最近的整数 FLOOR(9,9) = 9
CEIL 强制仅为最近的整数 CEIL(3.2) = 4
POWER 幂函数 POWER(2,3)
LOG 对数函数 LOG(7,3)
LN 对数函数 LN(1,0)
函数 功能 用例
SQRT 开平方 SQRT(9)
PI 圆周率 P10
SIN 三角函数 sin(1)
TAN 三角函数 TAN(1)
COT 三角函数 COT(1)
COS 三角函数 COS(2)
DADIANS 角度来换成角度 DADIANS(20)
DEGREES 弧度转换角度 DEGRESS(1)

获取系统的时间函数

  • NOW()函数能获得系统日期和时间,yyyy-MMMM-dd hh:mm:ss
  • CURDATE()函数能获得当前系统日期,yyyy=MM=ddd
  • CURTIME()函数能获得当前时间系统信息,hh:mm:ss

日期格式化函数(一)

  • **DATE_FORMAT()**函数用于格式化日期,返回用户想要的日期格式

    DATE_FORMAT(r日期,表达式)
    
    SELECT ename,DATE_FORMAT(hiredate,"%Y") AS "year"
    FROM t_emp; 
    

日期格式化函数(二)

占位符 作用 占位符 作用
%Y 年份 %m 月份
%d 日期 %w 星期(数字)
%W 星期(名称) %j 本年第几天
%U 本年第几周 %H 小时(24)
%h 小时(12) %i 分钟
%s %r 时间(12)
%T 时间(24)
  • 利用日期函数,查询明年你的生日是星期几?

    SELECT DATE_FORMAT("2019-6-20","%w"); #数字
    SELECT DATE_FORMAT("2019-6-20","%W"); #英文星期
    
  • 利用日期函数,查询1981年上半年入职的员工多少人?[聚合函数 全表范围不用分组GROUP BY]

    SELECT COUNT(*) FROM t_emp
    WHERE DATE_FORMAT(hiredate,"%Y")=1981
    AND DATE_FORMAT(hiredate,"%m")<=6 #上半年
    

日期计算的注意事项

  • MySQL数据库里面,两个日期不能直接加减,日期也不能与数字加减
日期偏移计算
  • DATE_ADD()函数可以实现日期的偏移计算,而且时间单位很灵活

    DATE_ADD(日期,INTERVAL 偏移量 时间单位)
    
    SELECT DATE_ADD(NOW(),INTERVAL 15 DAY); #15天之后
    SELECT DATE_ADD(NOW(),INTERVAL -300 MINUTE); #300分钟之前  2023-10-16 04:48:48
    SELECT DATE_FORMAT(
    DATE_ADD(DATE_ADD(NOW(),INTERVAL -6 MONTH),INTERVAL -3 DAY),"%Y/%m/%d"); #2023/04/13
    
计算日期之间相隔的天数
  • **DATEDIFF()**函数用来计算两个日期之间相差的天数

    DATEDIFF(日期,日期)
    

字符函数(一)

函数 功能 用例
LOWER 转换小写字符 LOWER(ename)
UPPER 转换大写字符 UPPER(ename)
LENGTH 字符数量 LENGTH(ename)
CONCAT 连接字符串 CONCAT(sal, “$”)
INSTR 字符出现的位置 INSTR(ename, “A”)
INSERT 插入/替换字符 INSERT(“你好”,1,0,”先生”)
替换1个字符 0表示不替换—候补
REPLACE 替换字符 REPLACE(“你好先生”,“先生”,”女士”)
先生换成女士
SELECT
  LOWER(ename),UPPER(ename),LENGTH(ename),CONCAT(sal,"$"),INSTR(ename,"A")
FROM t_emp;

字符函数(二)

函数 功能 用例
SUBSTR 截取字符串 SUBSTR(“你好世界”,3,4)
3开始位置 4结束位置
SUBSTRING 截取字符串 SUBSTRING(“你好世界”,3,2)
3个字符开始往后截取2个位置
LPAD 左侧填充字符 LPAD(“Hello”,10,”“)
* 10最终字符串一共为10个字符
电话隐私保护加※号
RPAD 右侧填充字符 RPAD(“Hello”,10,”*”)
TRIM 去除首位空格 TRIM(“ 你好先生 “)
SELECT LPAD(SUBSTRING("15027597319",8,4),11,"*");
SELECT RPAD(SUBSTRING("潘春尧",1,1),LENGTH("潘春尧")/3,"*"); 
#LENGTH是算英文字符 再除以3就是汉字的字符个数

条件函数

  • SQL语句中可以利用条件函数来实现变成语言里的条件判断

    IFNULL(表达式,值)
    IF(表达式,值1,值2)
    
  • 中秋节公司发放礼品,SALES部门发放礼品A,其余部门发放礼品B,打印每名员工获得的礼品
    [按部门名称作表连接 部门表和员工表 内连接没有部门的没有礼品]

    SELECT 
      e.empno,e.ename,d.dname,
      IF(d.dname="SALES","礼品A","礼品B")
    FROM t_emp e JOIN t_tept d ON e,deptno=d.deptno;
    

条件语句

  • 复杂的条件判断可以用条件语句来实现,比IF语句功能更强大

    CASE 
      WHEN 表达式 THEN 值1
      WHEN 表达式 THEN 值2
      ......
      ELSE 值N
    END
    
  • 公司年庆决定组织员工集体旅游,每个部门旅游目的地是不同的。SALES部门去P1地点,ACCOUNTING部门去P2地点,RESEARCH部门去P3地点,查询每名员工的旅行地点。

    SELECT 
      e.empno,e.ename,
      CASE 
        WHEN d.dname="SALES" THEN "p1"
        WHEN d.dname="ACCOUNTING" THEN "p2"
        WHEN d.dname="RESEARCH" THEN "P3"
    FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno;
    
课堂练习
  • 某公司决定为员工调整基本工资,具体调整方案如下:

    序号 条件 涨幅
    1 SALES部门中工龄超过20年 10%
    2 SALES部门中工龄不满20年 5%
    3 ACCOUNTING部门 +300元
    4 RESEARCH部门里低于部门平均底薪 +200元
    5 没有部门的员工 +100元
    [员工表连接部门表]
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    LEFT JOIN (SELECT deptno,AVG(sal) AS avg FROM t_temp GOURP BY deptno) t
    ON e.deptno=t.deptno
    SET sal=(
        CASE 
          WHEN d,dname="SALES" AND DATEDIFF(NOW(),e.hiredate)/365>=20 
          THEN e.sal*1.1
          WHEN d,dname="ALES" AND DATEDIFF(NOW(),e.hiredate)/365<20 
          THEN e.sal*1.50
          WHEN d,dname="ACCOUNTING" THEN e.sal+300
          WHEN d,dname="RESEARCH" THEN e.sal<t.avg THEN e.sal+200
          WHEN e.deotno IS NULL THEN e.sal+100
          ELSE e.sal
        END
    );
    

事务机制(一) 进入企业市场的第一步

避免写入直接操作数据文件
  • 如果数据的写入直接操作数据文件是非常危险的事情
利用日志来实现间接写入
  • MySQL总共有5种日志,其中只有redo日志和undo日志与事务有关
    [数据库 拷贝数据给 undo日志 记录修改 redo日志 与数据库同步数据]
事务机制(Transaction)
  • RDBMS = SQL语句 + 事务(ACID)
  • 事务是一个或者多个SQL语句组成的整体,要么全部执行成功,要么全都执行失败
事务案例
  • 把10部门中MANGER员工调往20部门,其他岗位的员工调往30部门,然后删除10部门
    事务开启事务 [把要修改的数据拷贝到undo日志[可恢复]内,做的修改会被记录到redo日志[同步]里面] UPDATE语句  DELETE语句  提交事务

管理事务

  • 默认情况下,MySQL执行每条SQL语句都会自动开启和提交事务

  • 为了让多条SQL语句纳入到一个事务之下,可以手动管理事务

    START TRANSACTION;
    SQL语句
    [COMMIT|ROLLBACK];
    
    START TRANSACTION;
    DELETE FROM t_emp
    DELETE FROM t_dept;
    SELECT * FROM t_emp;
    SELECT * FROM t_dept;
    #这些删除修改只是在redo日志文件中进行的修改并未提交 
    #虽然SELECT查不到了 但是点开左列的表中数据仍未删除
    #只要不提交事务 redo日志就不会和数据库做同步
    COMMIT; #把结果提交到日志里面 就会同步了
    ROLLBACK; #做标记 回滚 一起失败
    

事务的ACID属性

原子性 一致性 隔离性 持久性

事务的原子性

  • 一个事务中的所有操作要么全部完成,要么全部失败。事务执行后,不允许停留在中间某个状态

事务的一致性

  • 不管任何给定的时间、并发事务由多少,事务必须保证运行结果的一致性
    [阻止事务之间互相读取临时数据] [A給B 10元 事务没有提交后且回滚了 C给A20元 此时A应该有30元 ]

隔离性

  • 隔离性要求事务不受其他并发事务的影响,如同在给定的时间内,该事务是数据库唯一运行的事务
  • 默认情况下A事务,只能看到日志中该事务的相关数据 [A,B事务可以看undo和redo日志]

持久性

  • 事务一旦提交,结果便是永久性的。即便发生宕机,仍然可以依靠事务日志完成数据的持久化

事务机制(二)

事务的四个隔离级别(可设置相互读取)

序号 隔离级别 功能
1 read uncommitted 读取未提交数据
2 read committed 读取已提交数据
3 repeatable read 重复读取
4 serializable 序列化

业务案例1 [购票系统]

A事务 B事务
车次 车厢 坐席 状态
G8047 1 1A 未售出
G8047 1 1B 未售出

A事务看到G8047 1A坐席未售出 用UPDATE把状态修改成已售出 因为没有提交 所以只修改在了undo日志里 真实的数据没有发生改变。此时B事务启动了看到了1A坐席还有票 于是更新且提交了 于是数据已经发生了改变。

A事务
车次 车厢 坐席 状态
G8047 1 1A 已售出
G8047 1 1B 未售出
所以在这个案例中我们需要B事务去读取A事务的状态 发现在A事务的临时数据里购买了坐席 那么B事务就可以去购买其他的坐席

修改事务隔离级别

  • READ UNCOMMITTED 代表可以读取其他事务未提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    
    *无标题-查询1
    START TRANSACTION;
    UPDATE t_emp SET sal=1;
    -----------------------
    *无标题-查询2
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    #发现员工的sal并未变成1块钱
    
    #解决问题:增加事务隔离级别  一个事务读到了另一个事务的数据
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    COMMIT;
    

业务案例2 [银行转账]

Scott账户有5000元余额 B事务开始执行支出100元 此时A事务执行转账1000元到该用户 如果AB都正常commit 最终账户余额是5900元 但是如果B事务是一个错误的消费被回滚[退款操作] 最终账户应该是6000元。如果允许A事务去读取B事务的临时数据 按照4900+1000=5900元 若A回滚就凭空少了100元

修改事务隔离级别

  • READ COMMITTED 代表只能读取其他事务提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
    

业务案例3 [电商案例]

A事务 下单购买 商品(价格350元) B事务要对 该商品涨价

修改事务隔离级别【READ为默认级别】

  • REPEATABLE READ 代表事务在执行中反复读取数据,得到的结果是一致性的,不会受其他事务影响

    #就算事务A提交了数据 也不影响事务B的查询原始数据
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
    
START TRANSACTION; 
UPDATE t_emp SET sal=1; 
COMMIT;
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
START TRANSACTION; 
SELECT empno,ename,sal FROM t_emp; 
COMMIT;

事务的序列化

  • 由于事务并发执行所带来的各种问题,前三种隔离级别只适用于在某些业务场景中,但是序列化的隔离性,让事务逐一执行,就不会产生上述问题了。

    SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
START TRANSACTION; ①
UPDATE t_emp SET sal=1; ②
COMMIT; ⑤
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION; ③
SELECT empno,ename,sal FROM t_emp; ④ #此时会等待 只需要执行上方COMMIT
COMMIT;

数据导入与导出

数据导出与备份的区别

  • 数据导出,导出的纯粹是业务数据
  • 数据备份,备份的是数据文件、日志文件、索引文件灯

全量备份 → 增量备份1 → 增量备份2

数据导出的分类

数据库可以导出SQL文档(数据不多) 也可以 导出文本文档(数据巨大)

导出SQL文件 [或者手动右键库 存储SQL文件]

  • mysqldump用来把业务数据导出成SQL文件,其中也包括了表结构

    #导出命令行 不写no-date就导出即包含 表结构 又包含 数据
    mysqldump -uroot -p [no-date] 逻辑库 > 路径
    # D:/MySQL/demo.sql
    

导入SQL文件

  • source命令用于导入SQL文件,包括创建数据表,写入记录等
    sql > USE demo;
    sql > SOURCE backup.sql;
    
导出纯粹的业务数据

若数据巨大 先导出表结构 再导出数据库文档(.txt) 之后再导入 就会快很多

  • t_emp 右键 存储sql文件 仅结构 再次右键 导出向导… txt文档 (跳过词法分析与语法优化)

    "empno"    "ename"    "job"    "mgr"    "hiredate"    "sal"    "comm"    "deptno"
    "7369"    "SMITH"    "CLERK"    "7902"    "17/12/1980"    "800"        "20"
    "7499"    "ALLEN"    "SALESMAN"    "7698"    "20/2/1981"    "1600"    "300"    "30"
    "7521"    "WARD"    "SALESMAN"    "7698"    "22/2/1981"    "1250"    "500"    "30"
    "7566"    "JONES"    "MANAGER"    "7839"    "2/4/1981"    "2975"        "20"
    "7654"    "MARTIN"    "SALESMAN"    "7698"    "28/9/1981"    "1250"    "1400"    "30"
    "7698"    "BLAKE"    "MANAGER"    "7839"    "1/5/1981"    "2850"        "30"
    "7782"    "CLARK"    "MANAGER"    "7839"    "9/6/1981"    "2450"        "10"
    "7788"    "SCOTT"    "ANALYST"    "7566"    "9/12/1982"    "3000"        "20"
    "7839"    "KING"    "PRESIDENT"        "17/11/1981"    "5000"        "10"
    "7844"    "TURNER"    "SALESMAN"    "7698"    "8/9/1981"    "1500"    "0"    "30"
    "7876"    "ADAMS"    "CLERK"    "7788"    "12/1/1983"    "1100"        "20"
    "7900"    "JAMES"    "CLERK"    "7698"    "3/12/1981"    "950"        "30"
    "7902"    "FORD"    "ANALYST"    "7566"    "3/12/1981"    "3000"        "20"
    "7934"    "MILLER"    "CLERK"    "7782"    "23/1/1982"    "1300"        "10"
    
  • 导入回来 先删除t_emp 在dmeo右键 运行文件 导入刚刚右键存储的t_emp的结构(仅结构) 此时员工表结构就回来了 导入向导… 下一步 找到导出的txt文档 第一个数据行1(第一行有效数据) 在导入向导中根据源字段匹配目标字段 (txt+结构=数据表)

阅读全文
头像
Asuna
You are the one who can always get to me even with screen between us.